RecordingDevice: lend cached geometry instead of copying it per draw
A geometry-cache hit moved every vertex vector out of the cache into the draw (deep copy of positions/normals/uv/diffuse/indices/bones each frame). The cache now lends its vectors to the frame's first draw of that geometry and takes them back in Render3DBeginFrame; later draws of the same key+revision in the frame (terrain splat layers, shadow-map pass) are marked shared_geometry and carry no vectors -- the native host looks the GPU geometry up by key+revision, and draw captures expand them (Render3DExpandSharedGeometry). Index reads reuse a scratch buffer. Desktop live game: RenderGame rnd 1.7 -> 1.2 ms, no visual change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
44a020f01e
commit
6bfcac7d68
@@ -362,7 +362,9 @@ int run_live_client(
|
||||
renderer.touch_controller.update_screen_size(int(ui_w), int(ui_h));
|
||||
renderer.touch_controller.append_ui_commands(cap_ui);
|
||||
}
|
||||
native_draw_capture::write(capture_out, Render3DDraws(), cap_textures, ui_w, ui_h, cap_ui);
|
||||
std::vector<Render3DDraw> cap_draws = Render3DDraws();
|
||||
Render3DExpandSharedGeometry(cap_draws);
|
||||
native_draw_capture::write(capture_out, cap_draws, cap_textures, ui_w, ui_h, cap_ui);
|
||||
}
|
||||
|
||||
if (!screenshot_out.empty() && frames > 0) {
|
||||
|
||||
@@ -66,7 +66,7 @@ FixedFunctionState make_fixed_function_state(const Render3DDraw& draw) {
|
||||
const std::uint32_t fog_mode = draw.fog_enable
|
||||
? (draw.fog_table_mode ? draw.fog_table_mode : (draw.pretransformed ? 4u : draw.fog_vertex_mode)) : 0;
|
||||
state.flags = {1u, !draw.texture0.empty() ? 1u : 0u, !draw.texture1.empty() ? 1u : 0u, fog_mode};
|
||||
state.lighting_flags = {draw.lighting, draw.color_vertex, draw.diffuse.empty() ? 0u : 1u,
|
||||
state.lighting_flags = {draw.lighting, draw.color_vertex, (draw.shared_geometry ? draw.shared_diffuse : !draw.diffuse.empty()) ? 1u : 0u,
|
||||
draw.normalize_normals};
|
||||
state.material_sources = {draw.diffuse_material_source, draw.ambient_material_source,
|
||||
draw.emissive_material_source, draw.local_viewer};
|
||||
|
||||
+12
-3
@@ -8,6 +8,7 @@
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mt_host {
|
||||
@@ -191,14 +192,22 @@ void VulkanWindow::prepare_draws(const std::vector<Render3DDraw>& draws, const T
|
||||
back_buffer_list(in_scene).push_back(clear);
|
||||
continue;
|
||||
}
|
||||
decltype(geometries_)::iterator it;
|
||||
bool inserted = false;
|
||||
if (draw.shared_geometry) {
|
||||
// Repeats an earlier draw of this frame, which uploaded (or kept) the geometry.
|
||||
it = geometries_.find(GeometryId{draw.geometry_key, draw.geometry_revision});
|
||||
if (it == geometries_.end()) continue;
|
||||
} else {
|
||||
if (draw.positions.empty() || draw.indices.empty()) continue;
|
||||
const auto signature = draw.geometry_key ? draw.geometry_revision : geometry_hash(draw);
|
||||
const GeometryId id{draw.geometry_key, signature};
|
||||
auto [it, inserted] = geometries_.try_emplace(id);
|
||||
std::tie(it, inserted) = geometries_.try_emplace(GeometryId{draw.geometry_key, signature});
|
||||
}
|
||||
auto& geometry = it->second;
|
||||
if (inserted) {
|
||||
upload_geometry(draw, geometry);
|
||||
} else if (geometry.vertex_count != draw.positions.size() / 3 || geometry.index_count != draw.indices.size()) {
|
||||
} else if (!draw.shared_geometry && (geometry.vertex_count != draw.positions.size() / 3 ||
|
||||
geometry.index_count != draw.indices.size())) {
|
||||
throw std::runtime_error("geometry key/revision reused with a different size");
|
||||
}
|
||||
geometry.last_used_frame = frame_number_;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
@@ -15,6 +16,8 @@
|
||||
namespace {
|
||||
std::mutex g_draws_mutex;
|
||||
std::vector<Render3DDraw> g_draws;
|
||||
class RecordingDevice;
|
||||
RecordingDevice* g_recording_device = nullptr; // reclaims lent geometry in Render3DBeginFrame
|
||||
bool g_in_scene = false;
|
||||
int g_native_terrain_override = -1;
|
||||
bool g_gpu_render_targets = false;
|
||||
@@ -266,9 +269,12 @@ public:
|
||||
m_stageStates[0][D3DTSS_ALPHAARG1] = D3DTA_TEXTURE;
|
||||
m_stageStates[1][D3DTSS_COLOROP] = D3DTOP_DISABLE;
|
||||
m_stageStates[1][D3DTSS_ALPHAOP] = D3DTOP_DISABLE;
|
||||
g_recording_device = this;
|
||||
}
|
||||
~RecordingDevice() override
|
||||
{
|
||||
if (g_recording_device == this)
|
||||
g_recording_device = nullptr;
|
||||
m_renderTarget->Release();
|
||||
m_depthStencil->Release();
|
||||
m_backBuffer->Release();
|
||||
@@ -530,10 +536,10 @@ public:
|
||||
if (!m_stream)
|
||||
return E_FAIL;
|
||||
const UINT count = index_count(type, primitiveCount);
|
||||
std::vector<std::uint32_t> indices(count);
|
||||
m_indexScratch.resize(count);
|
||||
for (UINT i = 0; i < count; ++i)
|
||||
indices[i] = startVertex + i;
|
||||
record(type, primitiveCount, m_stream->bytes.data(), m_stream->bytes.size(), m_streamStride, indices,
|
||||
m_indexScratch[i] = startVertex + i;
|
||||
record(type, primitiveCount, m_stream->bytes.data(), m_stream->bytes.size(), m_streamStride, m_indexScratch,
|
||||
m_stream, nullptr, startVertex);
|
||||
return S_OK;
|
||||
}
|
||||
@@ -541,11 +547,10 @@ public:
|
||||
{
|
||||
if (!m_stream || !m_indices)
|
||||
return E_FAIL;
|
||||
std::vector<std::uint32_t> indices;
|
||||
if (!read_indices(m_indices->bytes.data(), m_indices->bytes.size(), m_indices->format, startIndex,
|
||||
index_count(type, primitiveCount), m_baseVertex, &indices))
|
||||
index_count(type, primitiveCount), m_baseVertex, &m_indexScratch))
|
||||
return E_FAIL;
|
||||
record(type, primitiveCount, m_stream->bytes.data(), m_stream->bytes.size(), m_streamStride, indices,
|
||||
record(type, primitiveCount, m_stream->bytes.data(), m_stream->bytes.size(), m_streamStride, m_indexScratch,
|
||||
m_stream, m_indices, startIndex);
|
||||
return S_OK;
|
||||
}
|
||||
@@ -904,24 +909,32 @@ private:
|
||||
}
|
||||
const std::uint64_t frame_id = UIRenderFrameId();
|
||||
auto cached = draw.geometry_key ? m_geometry_cache.find(draw.geometry_key) : m_geometry_cache.end();
|
||||
const bool reuse = cached != m_geometry_cache.end() && !cached->second.changing &&
|
||||
bool reuse = cached != m_geometry_cache.end() && !cached->second.changing &&
|
||||
cached->second.revision == draw.geometry_revision;
|
||||
if (reuse)
|
||||
{
|
||||
cached->second.last_used = frame_id;
|
||||
const Render3DDraw& prior = cached->second.geometry;
|
||||
draw.positions = prior.positions;
|
||||
draw.rhw = prior.rhw;
|
||||
draw.vertex_fog = prior.vertex_fog;
|
||||
draw.normals = prior.normals;
|
||||
draw.uv0 = prior.uv0;
|
||||
draw.uv1 = prior.uv1;
|
||||
draw.diffuse = prior.diffuse;
|
||||
draw.indices = prior.indices;
|
||||
draw.bone_indices = prior.bone_indices;
|
||||
draw.bone_weights = prior.bone_weights;
|
||||
// The cached vectors are lent to the frame's first draw of this geometry (no copy) and
|
||||
// taken back by reclaim() when the next frame begins; later draws of the same geometry in
|
||||
// this frame copy from that draw. A lent draw that never reached g_draws (a dropped shadow
|
||||
// draw) loses them: the vertices are extracted again below and reclaimed from that draw.
|
||||
GeometryCacheEntry& entry = cached->second;
|
||||
entry.last_used = frame_id;
|
||||
if (!entry.geometry.positions.empty())
|
||||
{
|
||||
move_geometry(entry.geometry, draw);
|
||||
entry.lent_index = g_draws.size();
|
||||
}
|
||||
else if (entry.lent_index < g_draws.size() && g_draws[entry.lent_index].geometry_key == draw.geometry_key &&
|
||||
g_draws[entry.lent_index].geometry_revision == draw.geometry_revision &&
|
||||
!g_draws[entry.lent_index].positions.empty())
|
||||
{
|
||||
draw.shared_geometry = true;
|
||||
draw.shared_diffuse = !g_draws[entry.lent_index].diffuse.empty();
|
||||
}
|
||||
else
|
||||
reuse = false;
|
||||
}
|
||||
if (!reuse)
|
||||
{
|
||||
draw.positions.resize(count * 3);
|
||||
if (layout.rhw) draw.rhw.resize(count);
|
||||
@@ -978,6 +991,8 @@ private:
|
||||
if (cached != m_geometry_cache.end())
|
||||
{
|
||||
cached->second.last_used = frame_id;
|
||||
if (!cached->second.changing && cached->second.revision == draw.geometry_revision)
|
||||
cached->second.lent_index = g_draws.size(); // re-extracted: later repeats share this draw
|
||||
if (!cached->second.changing && cached->second.revision != draw.geometry_revision)
|
||||
{
|
||||
cached->second.changing = true;
|
||||
@@ -985,7 +1000,9 @@ private:
|
||||
}
|
||||
}
|
||||
else if (draw.geometry_key && cached == m_geometry_cache.end())
|
||||
m_geometry_cache.emplace(draw.geometry_key, GeometryCacheEntry{draw.geometry_revision, frame_id, false, draw});
|
||||
// The new entry starts lent to this draw; reclaim() fills it when the next frame begins.
|
||||
m_geometry_cache.emplace(draw.geometry_key,
|
||||
GeometryCacheEntry{draw.geometry_revision, frame_id, false, Render3DDraw(), g_draws.size()});
|
||||
}
|
||||
if (gpu_skinned && skin_view.bone_matrices && skin_view.bone_count > 0)
|
||||
draw.bone_matrices.assign(skin_view.bone_matrices, skin_view.bone_matrices + std::size_t(skin_view.bone_count) * 16);
|
||||
@@ -1091,7 +1108,7 @@ private:
|
||||
{
|
||||
auto* surface = static_cast<CpuSurface*>(m_renderTarget);
|
||||
if (!surface->parent || surface->level != 0 || surface->desc.Format != D3DFMT_R5G6B5 || draw.lines ||
|
||||
draw.pretransformed || draw.indices.size() < 3)
|
||||
draw.pretransformed || (!draw.shared_geometry && draw.indices.size() < 3))
|
||||
return;
|
||||
draw.render_target = surface->parent->id;
|
||||
draw.target_width = surface->desc.Width;
|
||||
@@ -1143,7 +1160,42 @@ private:
|
||||
std::uint64_t last_used;
|
||||
bool changing;
|
||||
Render3DDraw geometry;
|
||||
std::size_t lent_index = SIZE_MAX; // g_draws slot holding the lent vectors this frame
|
||||
};
|
||||
|
||||
template <typename Fn>
|
||||
static void for_each_geometry_vector(Render3DDraw& a, Render3DDraw& b, Fn fn)
|
||||
{
|
||||
fn(a.positions, b.positions); fn(a.rhw, b.rhw); fn(a.vertex_fog, b.vertex_fog);
|
||||
fn(a.normals, b.normals); fn(a.uv0, b.uv0); fn(a.uv1, b.uv1); fn(a.diffuse, b.diffuse);
|
||||
fn(a.indices, b.indices); fn(a.bone_indices, b.bone_indices); fn(a.bone_weights, b.bone_weights);
|
||||
}
|
||||
static void move_geometry(Render3DDraw& from, Render3DDraw& to)
|
||||
{
|
||||
for_each_geometry_vector(from, to, [](auto& a, auto& b) { b = std::move(a); a.clear(); });
|
||||
}
|
||||
|
||||
public:
|
||||
// Render3DBeginFrame, before the last frame's draws are cleared: the cache takes back the vectors
|
||||
// it lent (or a re-extracted copy) from the draws that carry its geometry.
|
||||
void reclaim(std::vector<Render3DDraw>& draws)
|
||||
{
|
||||
for (auto& draw : draws)
|
||||
{
|
||||
if (!draw.geometry_key || draw.positions.empty())
|
||||
continue;
|
||||
auto it = m_geometry_cache.find(draw.geometry_key);
|
||||
if (it == m_geometry_cache.end())
|
||||
continue;
|
||||
GeometryCacheEntry& entry = it->second;
|
||||
entry.lent_index = SIZE_MAX;
|
||||
if (!entry.changing && entry.revision == draw.geometry_revision && entry.geometry.positions.empty())
|
||||
move_geometry(draw, entry.geometry);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::uint32_t> m_indexScratch;
|
||||
std::unordered_map<std::uint64_t, GeometryCacheEntry> m_geometry_cache;
|
||||
std::uint64_t m_cache_pruned_frame = 0;
|
||||
std::vector<float> m_offscreenDepth;
|
||||
@@ -1251,6 +1303,8 @@ std::string Render3DRenderTargetName(std::uint32_t id, std::uint32_t width, std:
|
||||
void Render3DBeginFrame()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_draws_mutex);
|
||||
if (g_recording_device)
|
||||
g_recording_device->reclaim(g_draws);
|
||||
g_draws.clear();
|
||||
}
|
||||
|
||||
@@ -1266,6 +1320,31 @@ void Render3DEndScene() { g_in_scene = false; }
|
||||
|
||||
const std::vector<Render3DDraw>& Render3DDraws() { return g_draws; }
|
||||
|
||||
void Render3DExpandSharedGeometry(std::vector<Render3DDraw>& draws)
|
||||
{
|
||||
std::unordered_map<std::uint64_t, std::size_t> owners; // geometry_key -> draw carrying the vectors
|
||||
for (std::size_t i = 0; i < draws.size(); ++i)
|
||||
{
|
||||
Render3DDraw& draw = draws[i];
|
||||
if (!draw.geometry_key)
|
||||
continue;
|
||||
if (!draw.shared_geometry)
|
||||
{
|
||||
if (!draw.positions.empty())
|
||||
owners[draw.geometry_key] = i;
|
||||
continue;
|
||||
}
|
||||
auto owner = owners.find(draw.geometry_key);
|
||||
if (owner == owners.end() || draws[owner->second].geometry_revision != draw.geometry_revision)
|
||||
continue;
|
||||
const Render3DDraw& from = draws[owner->second];
|
||||
draw.positions = from.positions; draw.rhw = from.rhw; draw.vertex_fog = from.vertex_fog;
|
||||
draw.normals = from.normals; draw.uv0 = from.uv0; draw.uv1 = from.uv1; draw.diffuse = from.diffuse;
|
||||
draw.indices = from.indices; draw.bone_indices = from.bone_indices; draw.bone_weights = from.bone_weights;
|
||||
draw.shared_geometry = false;
|
||||
}
|
||||
}
|
||||
|
||||
// D3D8 fixed-function texture coordinate processing for one stage (the native renderer's
|
||||
// native.vert applies the same rules on the GPU):
|
||||
// - D3DTSS_TEXCOORDINDEX low word picks the vertex set, a 2D set enters the transform as (u, v, 1, 0)
|
||||
|
||||
@@ -61,6 +61,12 @@ struct Render3DDraw {
|
||||
std::vector<float> bone_weights; // 4 floats per vertex
|
||||
std::vector<float> bone_matrices; // 16 floats per bone in the mesh's palette (D3D row-vector layout)
|
||||
bool lines = false;
|
||||
// A repeat, within one frame, of an earlier draw's geometry (same geometry_key and revision, e.g.
|
||||
// the terrain splat layers of a patch or a character's shadow-map pass): positions..bone_weights
|
||||
// are left empty and the consumer takes them from that earlier draw of Render3DDraws().
|
||||
// shared_diffuse says whether the geometry has vertex colours (!diffuse.empty() there).
|
||||
bool shared_geometry = false;
|
||||
bool shared_diffuse = false;
|
||||
|
||||
// D3DRS_* / D3DTSS_* values in effect for the draw.
|
||||
std::uint32_t alpha_blend = 0, src_blend = 0, dest_blend = 0;
|
||||
@@ -146,3 +152,6 @@ void Render3DAdd(Render3DDraw draw);
|
||||
void Render3DBeginScene();
|
||||
void Render3DEndScene();
|
||||
const std::vector<Render3DDraw>& Render3DDraws();
|
||||
// Fills the vectors of shared_geometry draws from the earlier draw that carries them (for consumers
|
||||
// that need every draw self-contained, e.g. a draw capture).
|
||||
void Render3DExpandSharedGeometry(std::vector<Render3DDraw>& draws);
|
||||
|
||||
Reference in New Issue
Block a user