diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 14:58:27 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 15:39:20 -0400 |
| commit | 2ad0830d2f044bc536155ae666f4477ba6d1f65f (patch) | |
| tree | 2ea161623a2ca1cb1e30d098f7aaa486161b220c /src | |
| parent | d2ac2275247a9ecb3e1404606f7026c6402533b8 (diff) | |
video_core: draw deferred instances before the state after their loop
A draw of N instances arrives as Begin(First)/End and N-1 Begin(Subsequent)/
End pairs. The draw manager draws the first at once and the rest together at
DrawDeferred, which ran at the next Begin(First) or when the macro returned.
NVN's draw macros reset the base instance register and its copy in
c[0][0x644] right after the loop, before returning, so the deferred instances
were drawn with base instance 0 and read the first records of the instance
buffer instead of their own.
In Mario Kart World every tree mesh is drawn this way, from one shared
instance buffer: each mesh got other meshes' transforms, which showed as
low-detail, inflated or scattered trees.
Any write other than the loop's own methods (draw begin/end, vertex and
index counts, inline indices) now draws the waiting instances first.
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/engines/draw_manager.cpp | 27 | ||||
| -rw-r--r-- | src/video_core/engines/maxwell_3d.cpp | 10 | ||||
| -rw-r--r-- | src/video_core/engines/maxwell_3d.h | 6 |
3 files changed, 43 insertions, 0 deletions
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index 5891c25622..038e5a2e69 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp @@ -80,6 +80,33 @@ void Maxwell3D::DrawManager::DrawDeferred(Maxwell3D& maxwell3d) { draw_state.instance_count = 0; } +bool Maxwell3D::DrawManager::IsInstanceLoopMethod(u32 method) noexcept { + switch (method) { + case MAXWELL3D_REG_INDEX(draw.begin): + case MAXWELL3D_REG_INDEX(draw.end): + case MAXWELL3D_REG_INDEX(vertex_buffer.first): + case MAXWELL3D_REG_INDEX(vertex_buffer.count): + case MAXWELL3D_REG_INDEX(index_buffer.first): + case MAXWELL3D_REG_INDEX(index_buffer.count): + case MAXWELL3D_REG_INDEX(index_buffer32_first): + case MAXWELL3D_REG_INDEX(index_buffer16_first): + case MAXWELL3D_REG_INDEX(index_buffer8_first): + case MAXWELL3D_REG_INDEX(index_buffer32_subsequent): + case MAXWELL3D_REG_INDEX(index_buffer16_subsequent): + case MAXWELL3D_REG_INDEX(index_buffer8_subsequent): + case MAXWELL3D_REG_INDEX(draw_inline_index): + case MAXWELL3D_REG_INDEX(inline_index_2x16.count): + case MAXWELL3D_REG_INDEX(inline_index_2x16.even): + case MAXWELL3D_REG_INDEX(inline_index_4x8.count): + case MAXWELL3D_REG_INDEX(inline_index_4x8.index0): + case MAXWELL3D_REG_INDEX(vertex_array_instance_first): + case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): + return true; + default: + return false; + } +} + void Maxwell3D::DrawManager::DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances) { draw_state.topology = topology; draw_state.vertex_buffer.first = vertex_first; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 6adfcf89b8..b344514bc6 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -336,6 +336,12 @@ void Maxwell3D::ConsumeSinkImpl(Core::System& system) { } void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) { + // The instances after the first are drawn together once their loop ends, and they must see + // the state they were begun with: NVN's draw macros reset the base instance (and its copy in + // c[0][0x644]) right after the loop, before the macro returns. + if (draw_manager.HasDeferredDraw() && !DrawManager::IsInstanceLoopMethod(method)) [[unlikely]] { + draw_manager.DrawDeferred(*this); + } regs.reg_array[method] = argument; for (auto const& table : dirty.tables) dirty.flags[table[method]] = true; @@ -516,6 +522,10 @@ void Maxwell3D::CallMultiMethod(Core::System& system, u32 method, const u32* bas ProcessMacro(system, method, base_start, amount, amount == methods_pending); return; } + // As in ProcessDirtyRegisters: the paths below write without it. + if (draw_manager.HasDeferredDraw() && !DrawManager::IsInstanceLoopMethod(method)) [[unlikely]] { + draw_manager.DrawDeferred(*this); + } switch (method) { case MAXWELL3D_REG_INDEX(const_buffer.buffer): case MAXWELL3D_REG_INDEX(const_buffer.buffer) + 1: diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 0121f1a01c..f21cf1d0ba 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -3083,6 +3083,12 @@ public: void ProcessMethodCall(Maxwell3D& maxwell3d, u32 method, u32 argument); void Clear(Maxwell3D& maxwell3d, u32 layer_count); void DrawDeferred(Maxwell3D& maxwell3d); + /// Instances after the first wait to be drawn together (DrawDeferred). + [[nodiscard]] bool HasDeferredDraw() const noexcept { + return draw_state.draw_mode == DrawMode::Instance && draw_state.instance_count != 0; + } + /// Methods an instance loop writes between its draws; any other write ends the loop. + [[nodiscard]] static bool IsInstanceLoopMethod(u32 method) noexcept; void DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances); void DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent); void DrawIndex(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, u32 base_instance, u32 num_instances); |
