Refactor composite source handling: unify link_composite_source logic and update related pipeline references

This commit is contained in:
iChris4 committed 2026-09-26 05:51:43 +02:00
1 parent f2f7c5007a
commit e89988f92e
7 files changed
+220 -132

No files matched your search

+6 -1
View File
@@ -761,7 +761,12 @@ off by the eye's own ground. An eye therefore skips the perspective draws of the
produced the depth copy where they were recorded and re-issues them in the composite's place,
with this eye's transforms, a constant-alpha blend (`kCompositeSourceAlpha`, about the strength of
the game's own composite) and a depth test against the world the eye has drawn by then: the ghost
in stereo, translucent, where the game put it. The desktop image is unchanged. The link is logged
in stereo, translucent, where the game put it. The desktop image is unchanged. The link is made as
the composite is recorded, so frames without a composite do no work for it, and a draw only
costs one comparison. The constant-alpha pipeline variants exist only for pipelines seen among
a composite's source draws: such a pipeline gets them from its next draw, and until then the
eye leaves that draw out, so a ghost first drawn in a session is missing from the eyes for that
one frame. The link is logged
once (`Immersive replay: pass N ... is re-issued by each eye`).
A reprojected 2D draw
+70 -72
View File
@@ -209,7 +209,7 @@ struct RenderPass {
bool snapshotColorResolveSource = false;
bool efbTarget = false;
// This pass's perspective draws are re-issued by an eye at the composite that samples its
// depth copy (link_composite_sources), and skipped where they were recorded.
// depth copy (link_composite_source), and skipped where they were recorded.
bool compositeSource = false;
std::vector<tex_palette_conv::ConvRequest> paletteConvs;
};
@@ -954,8 +954,57 @@ void end_offscreen() {
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
}
// Mario Kart Wii draws a ghost kart by rendering it alone into the cleared EFB, copying the
// frame's colour and depth out, drawing the race, and blending the copies back with one
// orthographic quad whose depth comes from the depth copy. That quad is a native effect, so an
// eye would stamp the desktop's flat image of the ghost over its own view, following the head
// and cut by the eye's ground. Instead the eye skips the draws of the pass that resolved the
// depth copy where they were recorded and re-issues them in the composite's place, blended
// with kCompositeSourceAlpha and depth-tested against its own world: the ghost in stereo,
// translucent, where the game put it.
//
// Linked as the composite is recorded, so a frame without one does no work for it. The source
// pass is complete by then, and only the current pass ever moves (offscreen suspension), so it
// is found by the copy it resolved. A source draw without constant-alpha siblings asks for them
// here; its pipeline's next draw brings them, and until then the eye leaves that draw out.
static void link_composite_source(const gx::DrawData& composite) noexcept {
if (g_currentRenderPass >= g_renderPasses.size()) {
return;
}
static bool linkLogged = false;
for (size_t p = g_currentRenderPass; p-- > 0;) {
auto& source = g_renderPasses[p];
if (!source.efbTarget || source.resolveTarget.get() != composite.uniformReplayLayout.compositeDepthCopy) {
continue;
}
size_t draws = 0;
for (const auto& command : source.commands) {
if (command.type != CommandType::Draw || command.data.draw.type != ShaderType::GX ||
!command.data.draw.gx.uniformReplayLayout.perspective) {
continue;
}
++draws;
if (command.data.draw.gx.stereoCompositeSourcePipeline == 0) {
gx::note_composite_source_pipeline(command.data.draw.gx.pipeline);
}
}
if (draws > 0) {
source.compositeSource = true;
if (!linkLogged) {
linkLogged = true;
Log.info("Immersive replay: pass {} ({} perspective draws) is re-issued by each eye in place of a "
"composite that samples its depth copy",
p, draws);
}
}
return;
}
}
template <>
void push_draw_command(gx::DrawData data) {
if (data.uniformReplayLayout.compositeDepthCopy != nullptr && stereo_frame_provider_active())
UNLIKELY { link_composite_source(data); }
push_draw_command(ShaderDrawCommand{.type = ShaderType::GX, .gx = data});
}
@@ -1650,66 +1699,6 @@ static bool prepare_stereo_replay_uniforms(const StereoReplayFrame& stereoFrame,
return true;
}
// Mario Kart Wii draws a ghost kart by rendering it alone into the cleared EFB, copying the
// frame's colour and depth out, drawing the race, and blending the copies back with one
// orthographic quad whose depth comes from the depth copy. That quad is a native effect, so an
// eye would stamp the desktop's flat image of the ghost over its own view, following the head
// and cut by the eye's ground. Instead the eye skips the draws of the pass that produced the
// depth copy where they were recorded and re-issues them in the composite's place, blended
// with kCompositeSourceAlpha and depth-tested against the eye's own world: the ghost in stereo,
// translucent, where the game put it. Runs once the frame's passes are complete, on the
// producer, before the frame is sealed.
static void link_composite_sources() noexcept {
if (g_stereoLocalPlayerCount > 1) {
return;
}
static bool linkLogged = false;
for (size_t i = 0; i < g_renderPasses.size(); ++i) {
if (!g_renderPasses[i].efbTarget) {
continue;
}
for (auto& command : g_renderPasses[i].commands) {
if (command.type != CommandType::Draw || command.data.draw.type != ShaderType::GX) {
continue;
}
auto& layout = command.data.draw.gx.uniformReplayLayout;
layout.compositeSourcePass = -1;
if (!layout.nativeEfbEffect || layout.perspective || layout.compositeDepthCopy == nullptr) {
continue;
}
for (size_t p = i; p-- > 0;) {
auto& source = g_renderPasses[p];
if (!source.efbTarget || source.resolveTarget.get() != layout.compositeDepthCopy) {
continue;
}
size_t draws = 0;
for (auto& sourceCommand : source.commands) {
if (sourceCommand.type != CommandType::Draw || sourceCommand.data.draw.type != ShaderType::GX) {
continue;
}
auto& sourceDraw = sourceCommand.data.draw.gx;
if (sourceDraw.uniformReplayLayout.perspective &&
gx::resolve_composite_source_pipelines(sourceDraw.configHash, sourceDraw.stereoGhostPipeline,
sourceDraw.stereoGhostStencilPipeline)) {
++draws;
}
}
if (draws > 0) {
source.compositeSource = true;
layout.compositeSourcePass = static_cast<int32_t>(p);
if (!linkLogged) {
linkLogged = true;
Log.info("Immersive replay: pass {} ({} perspective draws) is re-issued by each eye in place of the "
"composite in pass {} that samples its depth copy",
p, draws, i);
}
}
break;
}
}
}
}
static bool end_batch_impl(const wgpu::CommandEncoder& cmd, bool advanceFrame,
const StereoReplayFrame* stereoFrame = nullptr) {
ZoneScoped;
@@ -1729,9 +1718,6 @@ static bool end_batch_impl(const wgpu::CommandEncoder& cmd, bool advanceFrame,
const bool stereoPrepared = (stereoFrame == nullptr && !captureStereo) ||
prepare_stereo_replay_uniforms(stereoFrame != nullptr ? *stereoFrame : placeholder,
captureStereo ? &g_pendingLateStereo : nullptr);
if (stereoPrepared && (stereoFrame != nullptr || captureStereo)) {
link_composite_sources();
}
if (captureStereo && stereoPrepared) {
g_pendingLateStereo.generation = g_replayBufferGeneration.load(std::memory_order_acquire);
} else {
@@ -2697,9 +2683,20 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
static_cast<int32_t>(top), static_cast<int32_t>(targetSize.height)));
return std::array<uint32_t, 4>{left, top, right - left, bottom - top};
};
// Composites serve one camera; a multiplayer eye keeps every draw where it was recorded.
const bool compositeReplay = overrideTarget && !multiplayer;
const bool skipCompositeSource = compositeReplay && renderPasses[idx].compositeSource;
const auto find_composite_source = [&](const TextureRef* depthCopy) -> const RenderPass* {
for (u32 p = 0; p < idx; ++p) {
if (renderPasses[p].compositeSource && renderPasses[p].resolveTarget.get() == depthCopy) {
return &renderPasses[p];
}
}
return nullptr;
};
// The perspective draws of a composite's source pass, in the composite's place: their own
// recorded viewport and scissor, this eye's uniforms, the constant-alpha pipeline sibling,
// depth-tested against the world the eye has drawn by now (see link_composite_sources).
// depth-tested against the world the eye has drawn by now (see link_composite_source).
const auto replay_composite_source = [&](const RenderPass& source) {
const wgpu::Color alpha{stereo_replay::kCompositeSourceAlpha, stereo_replay::kCompositeSourceAlpha,
stereo_replay::kCompositeSourceAlpha, stereo_replay::kCompositeSourceAlpha};
@@ -2721,7 +2718,7 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
if (!draw.uniformReplayLayout.perspective) {
continue;
}
const gfx::PipelineRef pipeline = stencilTarget ? draw.stereoGhostStencilPipeline : draw.stereoGhostPipeline;
const gfx::PipelineRef pipeline = stencilTarget ? draw.stereoCompositeSourcePipeline : draw.compositeSourcePipeline;
if (pipeline == 0) {
continue;
}
@@ -2795,14 +2792,15 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
!stereo_replay::subviews_overlap(sourceScissor, playerRegion))) {
break;
}
// A composite's source pass is drawn at the composite instead (link_composite_sources).
if (overrideTarget && renderPasses[idx].compositeSource) {
// A composite's source pass is drawn at the composite instead (link_composite_source).
if (skipCompositeSource) {
break;
}
if (overrideTarget && draw.gx.uniformReplayLayout.compositeSourcePass >= 0 &&
static_cast<size_t>(draw.gx.uniformReplayLayout.compositeSourcePass) < renderPasses.size()) {
replay_composite_source(renderPasses[static_cast<size_t>(draw.gx.uniformReplayLayout.compositeSourcePass)]);
break;
if (compositeReplay && draw.gx.uniformReplayLayout.compositeDepthCopy != nullptr) {
if (const RenderPass* source = find_composite_source(draw.gx.uniformReplayLayout.compositeDepthCopy)) {
replay_composite_source(*source);
break;
}
}
const gfx::Range* uniformOverride = nullptr;
// Only a 2D draw the virtual screen actually claimed carries a stereo
+48 -43
View File
@@ -12,6 +12,7 @@
#include "../internal.hpp"
#include <absl/container/flat_hash_map.h>
#include <absl/container/flat_hash_set.h>
#include <tracy/Tracy.hpp>
#include <algorithm>
@@ -2210,6 +2211,11 @@ struct CachedPipelineState {
mutable gfx::PipelineRef stereoRef = 0;
mutable gfx::PipelineRef screenRef = 0;
mutable gfx::PipelineRef stereoScreenRef = 0;
// Constant-alpha siblings, while ref is in s_compositeSourcePipelines. Checked again only when
// compositeSourceGeneration falls behind that set (resolve_composite_source_pipelines).
mutable gfx::PipelineRef compositeSourceRef = 0;
mutable gfx::PipelineRef stereoCompositeSourceRef = 0;
mutable u32 compositeSourceGeneration = 0;
HashType configHash = 0;
// Carried here so the draw can be recorded without keeping the PipelineConfig that produced it alive; it is the only
// field of the config the draw itself still needs.
@@ -2278,47 +2284,45 @@ static const CachedPipelineState& resolve_pipeline_state(GXPrimitive prim, GXVtx
return state;
}
// The pipelines an eye re-issues at a composite (gfx::link_composite_source). Only the draw
// paths touch it, under the renderer lock they hold.
static absl::flat_hash_set<gfx::PipelineRef> s_compositeSourcePipelines;
// Advanced whenever that set changes, so each cached pipeline state checks its membership once.
static u32 s_compositeSourceGeneration = 1;
constexpr size_t kMaxCompositeSourcePipelines = 1024;
static void note_composite_source_pipeline_impl(gfx::PipelineRef pipeline) noexcept {
if (s_compositeSourcePipelines.size() >= kMaxCompositeSourcePipelines) {
s_compositeSourcePipelines.clear();
}
if (s_compositeSourcePipelines.insert(pipeline).second && ++s_compositeSourceGeneration == 0) {
s_compositeSourceGeneration = 1;
}
}
// A perspective draw pays one compare here. Its pipeline state builds the constant-alpha siblings
// from its own config, which is live now, once it is in the set; the config never has to be kept.
static void resolve_composite_source_pipelines(const CachedPipelineState& state) {
if (state.compositeSourceGeneration == s_compositeSourceGeneration)
LIKELY { return; }
state.compositeSourceGeneration = s_compositeSourceGeneration;
if (!s_compositeSourcePipelines.contains(state.ref)) {
state.compositeSourceRef = 0;
state.stereoCompositeSourceRef = 0;
return;
}
if (state.compositeSourceRef == 0) {
PipelineConfig config = *state.config;
config.stereoStencil = kCompositeSourceBlend;
state.compositeSourceRef = gfx::pipeline_ref(config);
config.stereoStencil = kCompositeSourceBlend | kStereoStencilFormat;
state.stereoCompositeSourceRef = gfx::pipeline_ref(config);
}
}
// Lazily cache eye-format siblings alongside the ordinary pipeline. Steady-state
// draws only read the refs: no extra config population/hashing on the Quest CPU.
// Shader modules are shared by the depth-format variants.
// The configs of the perspective draws recorded while a stereo frame provider is active, so a
// pass found later to be a composite's source (link_composite_sources) can have constant-alpha
// siblings built for exactly its draws. Nothing is compiled here; cached_pipeline_state's slot
// can be overwritten by the time the frame is sealed, which is why the config is kept by value.
struct CompositeSourceConfig {
PipelineConfig config{};
gfx::PipelineRef plain = 0;
gfx::PipelineRef stencil = 0;
};
static absl::flat_hash_map<HashType, CompositeSourceConfig> s_compositeSourceConfigs;
static std::mutex s_compositeSourceConfigsMutex;
constexpr size_t kMaxCompositeSourceConfigs = 4096;
static void remember_composite_source_config(const CachedPipelineState& state) {
std::lock_guard lock{s_compositeSourceConfigsMutex};
if (s_compositeSourceConfigs.contains(state.configHash)) return;
if (s_compositeSourceConfigs.size() >= kMaxCompositeSourceConfigs) s_compositeSourceConfigs.clear();
s_compositeSourceConfigs.emplace(state.configHash, CompositeSourceConfig{.config = *state.config});
}
static bool resolve_composite_source_pipelines_impl(HashType configHash, gfx::PipelineRef& plain,
gfx::PipelineRef& stencil) {
std::lock_guard lock{s_compositeSourceConfigsMutex};
const auto it = s_compositeSourceConfigs.find(configHash);
if (it == s_compositeSourceConfigs.end()) return false;
auto& entry = it->second;
if (entry.plain == 0) {
PipelineConfig config = entry.config;
config.stereoStencil = kCompositeSourceBlend;
entry.plain = gfx::pipeline_ref(config);
config.stereoStencil = kCompositeSourceBlend | kStereoStencilFormat;
entry.stencil = gfx::pipeline_ref(config);
}
plain = entry.plain;
stencil = entry.stencil;
return true;
}
static void resolve_replay_pipelines(const CachedPipelineState& state, bool screen) {
if (state.stereoRef && (!screen || state.stereoScreenRef)) return;
PipelineConfig config = *state.config;
@@ -2537,7 +2541,7 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
const bool stereo = aurora::stereo_frame_provider_active();
const bool screen = !replayLayout.perspective && !replayLayout.nativeEfbEffect;
if (stereo) resolve_replay_pipelines(pipelineState, screen);
if (stereo && replayLayout.perspective) remember_composite_source_config(pipelineState);
if (stereo && replayLayout.perspective) resolve_composite_source_pipelines(pipelineState);
s_lastDrawRecordedInterpolation = interpolationIdentityActive;
uint32_t instanceCount = 1;
@@ -2553,6 +2557,9 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
.exactScreenDepthPipeline = stereo && screen ? pipelineState.screenRef : 0,
.stereoPipeline = stereo ? pipelineState.stereoRef : 0,
.stereoScreenPipeline = stereo && screen ? pipelineState.stereoScreenRef : 0,
.compositeSourcePipeline = stereo && replayLayout.perspective ? pipelineState.compositeSourceRef : 0,
.stereoCompositeSourcePipeline =
stereo && replayLayout.perspective ? pipelineState.stereoCompositeSourceRef : 0,
.vertRange = vertRange,
.idxRange = idxRange,
.uniformRange = uniformRanges.current,
@@ -2560,7 +2567,6 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
.stereoUniformRanges = {},
.previousUniformRange = uniformRanges.previous,
.uniformReplayLayout = uniformRanges.replayLayout,
.configHash = pipelineState.configHash,
.vtxCount = vtxCount,
.indexCount = numIndices,
.instanceCount = instanceCount,
@@ -2738,7 +2744,6 @@ bool handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
} // namespace aurora::gx::fifo
bool aurora::gx::resolve_composite_source_pipelines(HashType configHash, gfx::PipelineRef& plain,
gfx::PipelineRef& stencil) {
return fifo::resolve_composite_source_pipelines_impl(configHash, plain, stencil);
void aurora::gx::note_composite_source_pipeline(gfx::PipelineRef pipeline) noexcept {
fifo::note_composite_source_pipeline_impl(pipeline);
}
+9 -8
View File
@@ -14,11 +14,11 @@ struct DrawData {
// Eye depth/stencil format siblings. Shader modules are shared with mono.
gfx::PipelineRef stereoPipeline = 0;
gfx::PipelineRef stereoScreenPipeline = 0;
// Constant-alpha siblings, resolved only for the draws of a composite's source pass
// (link_composite_sources) from the config remembered under configHash.
gfx::PipelineRef stereoGhostPipeline = 0;
gfx::PipelineRef stereoGhostStencilPipeline = 0;
HashType configHash = 0;
// Constant-alpha (kCompositeSourceBlend) siblings of pipeline and stereoPipeline, set only
// on the perspective draws of a pipeline an eye re-issues at a composite
// (note_composite_source_pipeline).
gfx::PipelineRef compositeSourcePipeline = 0;
gfx::PipelineRef stereoCompositeSourcePipeline = 0;
gfx::Range vertRange;
gfx::Range idxRange;
gfx::Range uniformRange;
@@ -101,9 +101,10 @@ void render(const DrawData& data, const wgpu::RenderPassEncoder& pass, DrawEncod
bool requireReadyPipeline, const gfx::Range* uniformRangeOverride = nullptr,
gfx::PipelineRef pipelineOverride = 0, const wgpu::Color* blendConstantOverride = nullptr);
// The constant-alpha (kCompositeSourceBlend) siblings of the pipeline recorded under configHash,
// for a stencil-less and a stencil eye target. False when the config is no longer remembered.
bool resolve_composite_source_pipelines(HashType configHash, gfx::PipelineRef& plain, gfx::PipelineRef& stencil);
// Gives the draws of `pipeline` their constant-alpha siblings from its next draw on: an eye
// re-issues them at a composite (gfx::link_composite_source). Called with the renderer lock
// the draw paths hold, as the composite is recorded.
void note_composite_source_pipeline(gfx::PipelineRef pipeline) noexcept;
void queue_surface(const u8* dlStart, uint32_t dlSize, bool bigEndian) noexcept;
} // namespace aurora::gx
+3 -6
View File
@@ -21,13 +21,10 @@ struct UniformReplayLayout {
// For a native effect that samples a freshly produced, frame-sized depth copy: that
// copy. Mario Kart Wii draws a ghost kart alone into the cleared EFB, copies the
// frame's colour and depth out, draws the race, then blends the copies back with one
// such quad. Set when the draw is recorded; the pass that produced the copy holds the
// ghost's own perspective draws.
// such quad. The pass that resolved the copy holds the ghost's own perspective draws,
// which an eye re-issues in this quad's place (gfx::link_composite_source) instead of
// stamping the desktop's flat image of the ghost over its own view.
const gfx::TextureRef* compositeDepthCopy = nullptr;
// Index of that pass once the frame is sealed (link_composite_sources), or -1. An eye
// re-issues its perspective draws in this composite's place instead of stamping the
// desktop's flat image of the ghost over its own view.
int32_t compositeSourcePass = -1;
};
struct UniformRanges {
+82 -1
View File
@@ -5308,7 +5308,6 @@ TEST_F(GXFifoTest, NativeCompositeRecordsTheFrameSizedDepthCopyItSamples) {
aurora::gx::FrameInterpolationDrawIdentity{}, false);
EXPECT_TRUE(composite.replayLayout.nativeEfbEffect);
EXPECT_EQ(composite.replayLayout.compositeDepthCopy, records[0].texture.get());
EXPECT_EQ(composite.replayLayout.compositeSourcePass, -1);
// The same draw with the game camera is world geometry, whatever it samples.
aurora::gfx::testing::reset_uniform_allocations();
@@ -5316,3 +5315,85 @@ TEST_F(GXFifoTest, NativeCompositeRecordsTheFrameSizedDepthCopyItSamples) {
aurora::gx::FrameInterpolationDrawIdentity{}, true);
EXPECT_EQ(world.replayLayout.compositeDepthCopy, nullptr);
}
static void draw_quad_with_projection(GXProjectionType type) {
aurora::gfx::testing::use_real_vertex_format_helpers(true);
aurora::gfx::testing::use_draw_command_tracking(true);
aurora::Mat4x4<float> proj{};
if (type == GX_PERSPECTIVE) {
proj.m0[0] = 1.0f;
proj.m1[1] = 1.0f;
proj.m2[2] = -1.0f;
proj.m2[3] = -1.0f;
proj.m3[2] = -1.0f;
} else {
proj.m0[0] = 2.0f / 608.0f;
proj.m0[3] = -1.0f;
proj.m1[1] = -2.0f / 456.0f;
proj.m1[3] = 1.0f;
proj.m2[2] = -1.0f;
proj.m3[3] = 1.0f;
}
GXSetProjection(&proj, type);
GXSetViewport(0.0f, 0.0f, 608.0f, 456.0f, 0.0f, 1.0f);
GXSetScissor(0, 0, 608, 456);
aurora::Mat3x4<float> identity{};
identity.m0[0] = identity.m1[1] = identity.m2[2] = 1.0f;
GXLoadPosMtxImm(&identity, GX_PNMTX0);
GXSetCurrentMtx(GX_PNMTX0);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetNumChans(0);
GXSetNumTexGens(0);
GXSetNumTevStages(1);
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0);
GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0);
GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
const float corners[4][2]{{-1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f}};
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
for (const auto& corner : corners) {
GXPosition3f32(corner[0], corner[1], -5.0f);
}
GXEnd();
}
TEST_F(GXFifoTest, CompositeSourcePipelineGetsConstantAlphaSiblingsFromItsNextDraw) {
// An eye re-issues MKW's ghost kart draws at the composite with constant-alpha siblings of
// their pipelines. Only a pipeline an eye asked for gets them, from its next draw on, and
// only in perspective draws.
draw_quad_with_projection(GX_PERSPECTIVE);
decode_fifo(flush_and_capture());
const auto* first = aurora::gfx::get_last_draw_command<aurora::gx::DrawData>();
ASSERT_NE(first, nullptr);
ASSERT_TRUE(first->uniformReplayLayout.perspective);
ASSERT_NE(first->pipeline, 0u);
EXPECT_EQ(first->compositeSourcePipeline, 0u);
EXPECT_EQ(first->stereoCompositeSourcePipeline, 0u);
const auto pipeline = first->pipeline;
aurora::gx::note_composite_source_pipeline(pipeline);
draw_quad_with_projection(GX_PERSPECTIVE);
decode_fifo(flush_and_capture());
const auto* next = aurora::gfx::get_last_draw_command<aurora::gx::DrawData>();
ASSERT_NE(next, nullptr);
ASSERT_EQ(next->pipeline, pipeline);
EXPECT_NE(next->compositeSourcePipeline, 0u);
EXPECT_NE(next->stereoCompositeSourcePipeline, 0u);
EXPECT_NE(next->compositeSourcePipeline, next->pipeline);
EXPECT_NE(next->stereoCompositeSourcePipeline, next->stereoPipeline);
EXPECT_NE(next->compositeSourcePipeline, next->stereoCompositeSourcePipeline);
draw_quad_with_projection(GX_ORTHOGRAPHIC);
decode_fifo(flush_and_capture());
const auto* flat = aurora::gfx::get_last_draw_command<aurora::gx::DrawData>();
ASSERT_NE(flat, nullptr);
ASSERT_EQ(flat->pipeline, pipeline);
EXPECT_EQ(flat->compositeSourcePipeline, 0u);
EXPECT_EQ(flat->stereoCompositeSourcePipeline, 0u);
}
+2 -1
View File
@@ -357,7 +357,8 @@ void push_draw_command<clear::DrawData>(clear::DrawData data) {
}
template <>
PipelineRef pipeline_ref<gx::PipelineConfig>(const gx::PipelineConfig& config) {
return 0;
// Distinct configs get distinct refs, as the real cache's config hash does.
return xxh3_hash(config, static_cast<HashType>(ShaderType::GX));
}
template <>
void push_draw_command<gx::DrawData>(gx::DrawData data) {