17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 14:37:29 +01:00

Give the terrain its own archive, its normals, and a level per tile

The tiles moved out of the scenery's pak into an archive of their own: a
sorted index of fixed-size entries found by where a tile sits in the
quadtree rather than by a name, each entry compressed on its own so a
tile can be read without touching the rest. A name apiece would have been
seventy megabytes of index to read before the first tile could be drawn.

An entry carries what its tile's ground asks for - how far it departs
from the finest mesh, and how long its triangles are - because which
level to draw has to be settled for tiles that are not on the card yet,
and the index is the only thing about them that is read up front. Until
now the figure came from the tile's level, which is the worst tile in it,
so one steep corner spoke for every tile that shared its level.

The mesh carries a normal per vertex, octahedron-encoded into two bytes,
in place of the face normal the fragment stage worked out from how the
view position changed across a triangle. A face normal makes the ground
read as facets, and a coarser level's facets face differently from the
ones they stand for, so the light over a hillside changed as the level
did. A geometry stage hands the fragment stage the three materials of the
triangle it is in and the barycentric weights, so ground changes over a
triangle rather than along its edge; 3.3 has no gl_BaryCoordEXT.

And the level a tile is drawn at now counts the camera's height. The
distance was measured on the ground plane alone, which puts the tile
underfoot nought metres away from a camera a kilometre above it - and
nought metres asks for the finest level there is, so a view from the air
wanted the whole ground at full detail, more tiles than the buffers hold,
and what would not fit came out as holes.

Archives written under the old rules are refused at the door and baked
again.
This commit is contained in:
maj00r
2026-09-20 21:22:03 +02:00
parent b877bc0772
commit 9408216ae7
21 changed files with 1604 additions and 550 deletions

View File

@@ -11,10 +11,14 @@ module;
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <vector>
#include "scene/quantizedmeshreader.h"
export module eu07.rendering.terrainmesharena;
import eu07.gl.buffer;
import eu07.gl.vao;
export {
@@ -26,7 +30,18 @@ export {
// bind and a draw per tile. Vertices are measured from one point for the whole terrain, which is
// what makes that possible.
//
// Indices are two bytes wide unless the arena is asked for four. An index is a quarter of a vertex but
// there are some six of them per vertex, so they are the larger half of what the terrain costs on the
// card - and a tile of fewer than sixty-five thousand vertices, which is nearly every tile, has no use
// for four bytes. The few that need more get an arena of their own and a draw call of their own.
//
// The buffers grow when a tile will not fit and nothing can be freed; they never shrink.
//
// Everything here goes through gl::vao and gl::buffer rather than through glBindVertexArray and
// glBindBuffer. The engine remembers what it last bound and skips a bind it believes is already in
// place, so a raw bind here leaves it drawing the scenery's geometry through this vertex array. And
// the element buffer binding belongs to whichever vertex array is bound, so writing indices without
// binding this one first overwrites the scenery's.
class terrain_mesh_arena {
public:
@@ -44,19 +59,29 @@ public:
terrain_mesh_arena( terrain_mesh_arena const & ) = delete;
terrain_mesh_arena &operator=( terrain_mesh_arena const & ) = delete;
bool create();
// Wideindices asks for four-byte indices, for tiles of more than 65536 vertices
bool create( bool Wideindices );
void destroy();
bool wideindices() const { return m_wideindices; }
// the most vertices a tile may have to fit this arena
std::uint32_t vertexlimit() const { return m_wideindices ? ( 1u << 24 ) : 65536u; }
// puts a tile's mesh in. returns a piece that is not held when it did not fit
piece put( quantizedmesh::reader::tile_data const &Tile );
void take( piece &Piece );
// bound for drawing; the index buffer goes with it
std::uint32_t vertexarray() const { return m_vao; }
void bind();
void unbind();
std::size_t vertexbytes() const { return m_vertexroom * sizeof( quantizedmesh::render_vertex ); }
std::size_t indexbytes() const { return m_indexroom * sizeof( std::uint32_t ); }
std::size_t bytes() const { return vertexbytes() + indexbytes(); }
// what the two buffers were allocated at. it only grows: gl has no way of giving a buffer back
// except by making a new one, and the pieces already in it would have to move
std::size_t indexwidth() const { return m_wideindices ? sizeof( std::uint32_t ) : sizeof( std::uint16_t ); }
std::size_t roombytes() const {
return m_vertexroom * sizeof( quantizedmesh::render_vertex ) + m_indexroom * indexwidth(); }
// and what the tiles in them actually take up, which is what a budget has to be measured against
std::size_t usedbytes() const {
return m_verticesheld * sizeof( quantizedmesh::render_vertex ) + m_indicesheld * indexwidth(); }
std::size_t verticesheld() const { return m_verticesheld; }
private:
@@ -74,14 +99,22 @@ private:
bool grow_vertices( std::uint32_t Wanted );
bool grow_indices( std::uint32_t Wanted );
std::uint32_t m_vao { 0 };
std::uint32_t m_vertexbuffer { 0 };
std::uint32_t m_indexbuffer { 0 };
// attribute places the shader reads, and what one vertex is made of
void describe_vertices();
// gl::buffer cannot be moved - it deletes what it holds - so growing one means putting a new one
// in its place rather than handing it over
std::optional<gl::vao> m_vao;
std::unique_ptr<gl::buffer> m_vertexbuffer;
std::unique_ptr<gl::buffer> m_indexbuffer;
std::uint32_t m_vertexroom { 0 };
std::uint32_t m_indexroom { 0 };
span_list m_freevertices;
span_list m_freeindices;
std::size_t m_verticesheld { 0 };
std::size_t m_indicesheld { 0 };
bool m_wideindices { false };
std::vector<std::uint16_t> m_narrow; // scratch for the common case, so a put allocates nothing
};
} // export