Revert "feat(render): Campaign V slice V4c - move the world draw path onto the RHI"

This reverts commit f353fb53f8.
This commit is contained in:
Erik 2026-07-27 22:38:02 +02:00
parent b537f3a952
commit 543bc79f8a
8 changed files with 1172 additions and 1215 deletions

View file

@ -680,8 +680,7 @@ internal sealed class LivePresentationCompositionPhase
"WB draw dispatcher",
() => new WbDrawDispatcher(
d.Gl,
host.GpuDevice,
host.GpuFrameLifetime,
foundation.MeshShader,
foundation.TextureCache,
foundation.MeshAdapter,
entitySpawnAdapter,
@ -856,12 +855,10 @@ internal sealed class LivePresentationCompositionPhase
"environment-cell renderer",
() => new EnvCellRenderer(
d.Gl,
host.GpuDevice,
host.GpuFrameLifetime,
foundation.MeshAdapter.MeshManager!,
envCellFrustum),
static value => value.Dispose());
envCellLease.Resource.Initialize();
envCellLease.Resource.Initialize(foundation.MeshShader);
Fault(LivePresentationCompositionPoint.EnvironmentCellsCreated);
var landblockRenderPublisher = new LandblockRenderPublisher(

View file

@ -190,30 +190,6 @@ internal sealed class GlGpuDevice : IGpuDevice
string fragmentPath = Path.Combine(_shadersDirectory, $"{description.Shaders.Name}.frag");
string vertexSource = File.ReadAllText(vertexPath);
string fragmentSource = File.ReadAllText(fragmentPath);
// Campaign V slice V4c: splice the slice-V2 shared preamble
// (Shaders/common.glsl) into every pipeline, using Shader's own
// InjectPreamble so a pipeline-compiled program and a
// Shader-compiled one are built from byte-identical sources.
//
// mesh_modern REQUIRES it — the preamble declares the binding-9
// texture table and defines ACDREAM_TEXTURE_HANDLE / ACDREAM_UBO_SET,
// so without it the world shaders do not compile at all. Applying it
// unconditionally rather than per-pipeline keeps one rule: every
// shader this backend compiles sees the same preamble, which is also
// what the Vulkan backend gets for free once the sources are compiled
// to .spv. For shaders that reference none of it (ui_text,
// debug_line) the added text is an unused SSBO declaration and two
// macro definitions — every shader in the tree is #version 430 core,
// so the std430 declaration is always legal.
string commonPath = Path.Combine(_shadersDirectory, "common.glsl");
if (File.Exists(commonPath))
{
string commonSource = File.ReadAllText(commonPath);
vertexSource = Shader.InjectPreamble(vertexSource, commonSource);
fragmentSource = Shader.InjectPreamble(fragmentSource, commonSource);
}
return new GlGpuPipeline(_gl, Retirement, description, vertexSource, fragmentSource);
}
@ -321,31 +297,14 @@ internal sealed class GlGpuDevice : IGpuDevice
"only ever renders single-sampled targets.");
}
// Campaign V slice V4c: a null Color.Target means "whatever the frame
// spine has bound", NOT "framebuffer 0" — so this deliberately does
// not rebind. GpuPassDescription's own remarks say the transitional
// path keeps "clears and framebuffer management" with the spine, and
// forcing 0 here breaks that: PrivateEntityViewportRenderer binds its
// offscreen FBO and then calls WbDrawDispatcher.Draw (see that file's
// RenderToTexture), as does PortalTunnelPresentation. Once the
// dispatcher records through an encoder, binding 0 on BeginPass would
// redirect the paperdoll and creature-appraisal viewports to the
// backbuffer and leave their textures empty — and the offline pixel
// gate does not cover those viewports, so it would have shipped
// silently. This is the same class of fix as the ambient-capability
// save/restore in GlGpuPassEncoder (plan §7.1 rule 1): while raw-GL
// renderers still own framebuffers, the backend preserves what they
// bound rather than asserting its own. Removed at V4h, when the spine
// declares real passes and a target is always explicit.
//
// An explicit Target still binds, because then the caller HAS named
// the attachment.
uint framebuffer = 0;
if (description.Color.Target is { } target)
{
if (target is not GlGpuRenderTarget glTarget)
throw new ArgumentException("The GL backend can only render into a GL render target.");
_gl.BindFramebuffer(GLEnum.Framebuffer, glTarget.GlFramebufferName);
framebuffer = glTarget.GlFramebufferName;
}
_gl.BindFramebuffer(GLEnum.Framebuffer, framebuffer);
bool clearsColor = description.Color.Load == GpuLoadOp.Clear;
bool clearsDepth = description.Depth is { Load: GpuLoadOp.Clear };

View file

@ -249,7 +249,7 @@ public static class RenderBootstrap
// --- WbDrawDispatcher (GameWindow ~2377-2381) ---
var drawDispatcher = new Wb.WbDrawDispatcher(
gl, gpuDevice, gpuFrameLifetime, textureCache, meshAdapter, entitySpawnAdapter,
gl, meshShader, textureCache, meshAdapter, entitySpawnAdapter,
bindless, classificationCache, translucencyFades);
drawDispatcher.AlphaToCoverage = opts.Quality.AlphaToCoverage;

View file

@ -52,16 +52,7 @@ public sealed class Shader : IDisposable
/// preamble cannot simply be prepended — it has to land after that block,
/// before the first real declaration.
/// </summary>
/// <remarks>
/// Campaign V slice V4c widened this from <c>private</c> to
/// <c>internal</c> so <c>GlGpuDevice.CreatePipeline</c> splices the
/// preamble with the SAME code rather than a second copy of the rule.
/// mesh_modern needs the preamble (it calls ACDREAM_TEXTURE_HANDLE and
/// ACDREAM_UBO_SET), and a pipeline-compiled copy that differed from the
/// <see cref="Shader"/>-compiled one by even a line would be a silent
/// divergence between two programs that must stay identical.
/// </remarks>
internal static string InjectPreamble(string source, string preamble)
private static string InjectPreamble(string source, string preamble)
{
int insertAt = 0;
int lineStart = 0;

File diff suppressed because it is too large Load diff

View file

@ -171,23 +171,6 @@ public sealed class GlobalMeshBuffer : IDisposable
/// <summary>The index store's raw GL name. See <see cref="VBO"/>.</summary>
public uint IBO => _indexBuffer is null ? 0u : RequireGlBuffer(_indexBuffer).GlName;
/// <summary>
/// The vertex store as the arena actually owns it. Campaign V slice V4c's
/// draw paths bind this through <c>IGpuPassEncoder.BindVertexBuffer</c>;
/// its layout is already byte-identical to
/// <c>GpuVertexLayout.WorldMesh</c> (32-byte stride, locations 0/1/2 at
/// offsets 0/12/24 — see <see cref="ConfigureVertexAttributes"/>), so the
/// pipeline's own VAO describes the same vertices this arena packs.
///
/// <see cref="VBO"/>/<see cref="IBO"/> survive alongside these because
/// <c>ParticleRenderer</c> and <c>ObjectMeshManager</c> still bind the raw
/// names; that bridge retires with them, not with this slice.
/// </summary>
internal IGpuBuffer? VertexStore => _vertexBuffer;
/// <summary>The index store as the arena owns it. See <see cref="VertexStore"/>.</summary>
internal IGpuBuffer? IndexStore => _indexBuffer;
internal long UploadCount { get; private set; }
internal long UploadedBytes { get; private set; }
internal long CapacityBytes =>

File diff suppressed because it is too large Load diff

View file

@ -110,14 +110,14 @@ public class EnvCellRendererTests
{
// GL and meshManager are null — only valid for pure-data tests (no
// Initialize() is called, so no GL calls are made).
var r = new EnvCellRenderer(gl: null!, device: null!, frameSource: null!, meshManager: null!, frustum: new WbFrustum());
var r = new EnvCellRenderer(gl: null!, meshManager: null!, frustum: new WbFrustum());
Assert.True(r.NeedsPrepare);
}
[Fact]
public void NewRenderer_NotDisposed()
{
var r = new EnvCellRenderer(gl: null!, device: null!, frameSource: null!, meshManager: null!, frustum: new WbFrustum());
var r = new EnvCellRenderer(gl: null!, meshManager: null!, frustum: new WbFrustum());
Assert.False(r.IsDisposed);
}
@ -128,7 +128,7 @@ public class EnvCellRendererTests
[Fact]
public void RemoveLandblock_NonExistent_DoesNotThrow()
{
var r = new EnvCellRenderer(gl: null!, device: null!, frameSource: null!, meshManager: null!, frustum: new WbFrustum());
var r = new EnvCellRenderer(gl: null!, meshManager: null!, frustum: new WbFrustum());
// Should silently no-op.
r.RemoveLandblock(0xA9B40000u);
Assert.True(r.NeedsPrepare);
@ -218,7 +218,7 @@ public class EnvCellRendererTests
// Reflection-based test that drives the private GetPooledList +
// _poolIndex/_listPool fields. If a future refactor removes the
// Clear() call, this test fails.
var r = new EnvCellRenderer(gl: null!, device: null!, frameSource: null!, meshManager: null!, frustum: new WbFrustum());
var r = new EnvCellRenderer(gl: null!, meshManager: null!, frustum: new WbFrustum());
var type = typeof(EnvCellRenderer);
var getPooledListMethod = type.GetMethod("GetPooledList",
@ -249,7 +249,7 @@ public class EnvCellRendererTests
{
// Sanity check for the fresh-list branch. _poolIndex past _listPool.Count
// should produce a brand-new empty list and grow the pool.
var r = new EnvCellRenderer(gl: null!, device: null!, frameSource: null!, meshManager: null!, frustum: new WbFrustum());
var r = new EnvCellRenderer(gl: null!, meshManager: null!, frustum: new WbFrustum());
var type = typeof(EnvCellRenderer);
var getPooledListMethod = type.GetMethod("GetPooledList",
@ -338,7 +338,7 @@ public class EnvCellRendererTests
[Fact]
public void NewRenderer_SnapshotGenerationStartsAtZero()
{
var r = new EnvCellRenderer(gl: null!, device: null!, frameSource: null!, meshManager: null!, frustum: new WbFrustum());
var r = new EnvCellRenderer(gl: null!, meshManager: null!, frustum: new WbFrustum());
Assert.Equal(0, r.SnapshotGeneration);
}
}