mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
Refactor composite source handling: unify link_composite_source logic and update related pipeline references
This commit is contained in:
1 parent
f2f7c5007a
commit
e89988f92e
7 files changed
+220
-132
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user