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

+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) {