aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 14:58:27 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 15:39:20 -0400
commit2ad0830d2f044bc536155ae666f4477ba6d1f65f (patch)
tree2ea161623a2ca1cb1e30d098f7aaa486161b220c /src
parentd2ac2275247a9ecb3e1404606f7026c6402533b8 (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.cpp27
-rw-r--r--src/video_core/engines/maxwell_3d.cpp10
-rw-r--r--src/video_core/engines/maxwell_3d.h6
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);