feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -16,8 +16,8 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// <para>Two structural differences from V4c. It records into the pass
|
||||
/// <c>VulkanWorldScenePhase</c> opened rather than opening
|
||||
/// <c>"envcell-shells"</c> of its own, because the frame's one backbuffer pass
|
||||
/// resolves. And there is no binding-9 texture table: V4t moved the slot onto
|
||||
/// the device, and on Vulkan that table is set 2, which the encoder binds.</para>
|
||||
/// resolves. And the global texture table is set 2 rather than a storage
|
||||
/// buffer: storage binding 9 is now #226's per-instance detail category.</para>
|
||||
/// </summary>
|
||||
public sealed unsafe partial class EnvCellRenderer
|
||||
{
|
||||
|
|
@ -27,10 +27,14 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
private IGpuPipeline? _opaquePipeline;
|
||||
private IGpuPipeline? _alphaPipeline;
|
||||
private IGpuPipeline? _additivePipeline;
|
||||
private IGpuPipeline? _detailPipeline;
|
||||
private IGpuPipeline? _transparentDetailPipeline;
|
||||
private readonly TerrainAtlas.RetailDetailTextureBinding _environmentDetail;
|
||||
private readonly Func<bool> _buildingDetailEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// The RHI arm's constructor. It also completes <c>Initialize</c>'s job: the
|
||||
/// three pipelines ARE this renderer's program, so there is no second step
|
||||
/// five pipelines ARE this renderer's program, so there is no second step
|
||||
/// and no <c>Shader</c> to hand in.
|
||||
/// </summary>
|
||||
internal EnvCellRenderer(
|
||||
|
|
@ -38,13 +42,17 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
ICurrentGpuFrameSource frames,
|
||||
IWorldPassScope scope,
|
||||
ObjectMeshManager meshManager,
|
||||
WbFrustum frustum)
|
||||
WbFrustum frustum,
|
||||
TerrainAtlas.RetailDetailTextureBinding environmentDetail = default,
|
||||
Func<bool>? buildingDetailEnabled = null)
|
||||
{
|
||||
_device = device ?? throw new ArgumentNullException(nameof(device));
|
||||
_frames = frames ?? throw new ArgumentNullException(nameof(frames));
|
||||
_scope = scope ?? throw new ArgumentNullException(nameof(scope));
|
||||
_meshManager = meshManager ?? throw new ArgumentNullException(nameof(meshManager));
|
||||
_frustum = frustum ?? throw new ArgumentNullException(nameof(frustum));
|
||||
_environmentDetail = environmentDetail;
|
||||
_buildingDetailEnabled = buildingDetailEnabled ?? DisableDetailTextures;
|
||||
|
||||
_opaquePipeline = CreateShellPipeline(
|
||||
device, "envcell-opaque", GpuBlendMode.None, depthWrite: true, scope.SampleCount);
|
||||
|
|
@ -52,9 +60,29 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
device, "envcell-alpha", GpuBlendMode.StraightAlpha, depthWrite: false, scope.SampleCount);
|
||||
_additivePipeline = CreateShellPipeline(
|
||||
device, "envcell-additive", GpuBlendMode.Additive, depthWrite: false, scope.SampleCount);
|
||||
_detailPipeline = CreateShellPipeline(
|
||||
device,
|
||||
"envcell-retail-detail",
|
||||
GpuBlendMode.RetailDetail,
|
||||
depthWrite: true,
|
||||
scope.SampleCount,
|
||||
shaderName: "mesh_detail",
|
||||
depthCompare: RetailDetailTextureContract.DetailDepthCompare(
|
||||
transparent: false));
|
||||
_transparentDetailPipeline = CreateShellPipeline(
|
||||
device,
|
||||
"envcell-retail-detail-alpha",
|
||||
GpuBlendMode.RetailDetail,
|
||||
depthWrite: false,
|
||||
scope.SampleCount,
|
||||
shaderName: "mesh_detail",
|
||||
depthCompare: RetailDetailTextureContract.DetailDepthCompare(
|
||||
transparent: true));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private static bool DisableDetailTextures() => false;
|
||||
|
||||
/// <summary>
|
||||
/// One pipeline per blend state the shell pass uses. Everything else is
|
||||
/// shared: <c>mesh_modern</c>, the 32-byte world-mesh vertex, triangle lists,
|
||||
|
|
@ -70,15 +98,17 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
string name,
|
||||
GpuBlendMode blend,
|
||||
bool depthWrite,
|
||||
int sampleCount) =>
|
||||
int sampleCount,
|
||||
string shaderName = "mesh_modern",
|
||||
GpuCompareOp depthCompare = GpuCompareOp.Less) =>
|
||||
device.CreatePipeline(new GpuPipelineDescription
|
||||
{
|
||||
Name = name,
|
||||
Shaders = new GpuShaderSet("mesh_modern"),
|
||||
Shaders = new GpuShaderSet(shaderName),
|
||||
VertexLayout = GpuVertexLayout.WorldMesh,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = blend,
|
||||
Depth = new GpuDepthState(Test: true, Write: depthWrite, GpuCompareOp.Less),
|
||||
Depth = new GpuDepthState(Test: true, Write: depthWrite, depthCompare),
|
||||
Cull = GpuCullMode.Back,
|
||||
FrontFace = GpuFrontFace.Clockwise,
|
||||
AlphaToCoverage = false,
|
||||
|
|
@ -196,6 +226,7 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
BindRingSection<int>(
|
||||
encoder, frame, GpuBindingModel.StorageInstanceLightSets,
|
||||
_lightSetData.AsSpan(0, uniqueInstanceCount * lightStride));
|
||||
BindEnvironmentDetailCategory(encoder, frame);
|
||||
|
||||
// The frame-global sections, bound after this renderer's own binds
|
||||
// because those binds are what select the descriptor scope.
|
||||
|
|
@ -210,6 +241,9 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
MemoryMarshal.AsBytes(_commands.AsSpan(0, totalDraws)).CopyTo(commands.Data);
|
||||
IGpuBuffer commandBuffer = commands.Buffer;
|
||||
uint commandBase = commands.OffsetBytes;
|
||||
bool detailEnabled = RetailDetailTextureContract.ShouldRender(
|
||||
_buildingDetailEnabled(),
|
||||
_environmentDetail);
|
||||
|
||||
for (int drawRangeIndex = 0; drawRangeIndex < _mdiDrawRanges.Count; drawRangeIndex++)
|
||||
{
|
||||
|
|
@ -222,13 +256,16 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
if (cullMode == CullMode.Landblock) cullMode = CullMode.None;
|
||||
|
||||
bool isAdditive = groupIndex >= 4;
|
||||
IGpuPipeline rangeBasePipeline = isAdditive
|
||||
? _additivePipeline!
|
||||
: _alphaPipeline!;
|
||||
if (renderPass == WbRenderPass.Transparent)
|
||||
{
|
||||
// Blend state is the pipeline's; switching variants mid-pass has
|
||||
// to re-establish the mesh, which is vertex-array state.
|
||||
BindPipelineWithMesh(
|
||||
encoder,
|
||||
isAdditive ? _additivePipeline! : _alphaPipeline!,
|
||||
rangeBasePipeline,
|
||||
mesh);
|
||||
}
|
||||
|
||||
|
|
@ -241,12 +278,85 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
: (int)renderPass;
|
||||
pushConstants.DrawIdOffset = drawRange.FirstCommand;
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
|
||||
// Retail DrawMesh's two-pass fallback redraws each transparent
|
||||
// RenderMeshSubset immediately, before the next delayed-alpha
|
||||
// subset. Preserve that base/detail adjacency so another shell or
|
||||
// particle cannot be composited between the two contributions.
|
||||
if (renderPass == WbRenderPass.Transparent && detailEnabled)
|
||||
{
|
||||
int rangeEnd = drawRange.FirstCommand + drawRange.CommandCount;
|
||||
for (int command = drawRange.FirstCommand; command < rangeEnd; command++)
|
||||
{
|
||||
BindPipelineWithMesh(encoder, rangeBasePipeline, mesh);
|
||||
SetCullMode(encoder, cullMode);
|
||||
pushConstants.RenderPass = isAdditive
|
||||
? (int)renderPass | 0x100
|
||||
: (int)renderPass;
|
||||
pushConstants.DrawIdOffset = command;
|
||||
pushConstants.TextureIndexA = 0;
|
||||
pushConstants.ParamA = 0f;
|
||||
pushConstants.ParamB = 0f;
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
encoder.MultiDrawIndexedIndirect(
|
||||
commandBuffer,
|
||||
commandBase + (uint)(command * sizeof(DrawElementsIndirectCommand)),
|
||||
1,
|
||||
(uint)sizeof(DrawElementsIndirectCommand));
|
||||
|
||||
BindPipelineWithMesh(encoder, _transparentDetailPipeline!, mesh);
|
||||
SetCullMode(encoder, cullMode);
|
||||
pushConstants.DrawIdOffset = command;
|
||||
pushConstants.TextureIndexA = _environmentDetail.TextureSlot.Index;
|
||||
pushConstants.ParamA = _environmentDetail.Tiling;
|
||||
pushConstants.ParamB = 0f;
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
encoder.MultiDrawIndexedIndirect(
|
||||
commandBuffer,
|
||||
commandBase + (uint)(command * sizeof(DrawElementsIndirectCommand)),
|
||||
1,
|
||||
(uint)sizeof(DrawElementsIndirectCommand));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
encoder.MultiDrawIndexedIndirect(
|
||||
commandBuffer,
|
||||
commandBase + (uint)(drawRange.FirstCommand * sizeof(DrawElementsIndirectCommand)),
|
||||
(uint)drawRange.CommandCount,
|
||||
(uint)sizeof(DrawElementsIndirectCommand));
|
||||
}
|
||||
|
||||
// Retail DrawEnvCell category (2). Replay the already-filtered opaque
|
||||
// shell commands, including ClipMap built-mesh subsets, and apply the
|
||||
// 10-50 m positive-view-depth fade. The existing
|
||||
// "Building Detail Textures" option gates both this and buildings,
|
||||
// matching LScape::ChangeRegion.
|
||||
if (renderPass == WbRenderPass.Opaque
|
||||
&& detailEnabled)
|
||||
{
|
||||
BindPipelineWithMesh(encoder, _detailPipeline!, mesh);
|
||||
pushConstants.RenderPass = 0;
|
||||
pushConstants.TextureIndexA = _environmentDetail.TextureSlot.Index;
|
||||
pushConstants.ParamA = _environmentDetail.Tiling;
|
||||
pushConstants.ParamB = 0f;
|
||||
|
||||
for (int drawRangeIndex = 0; drawRangeIndex < _mdiDrawRanges.Count; drawRangeIndex++)
|
||||
{
|
||||
MdiDrawRange drawRange = _mdiDrawRanges[drawRangeIndex];
|
||||
var cullMode = (CullMode)(drawRange.GroupIndex % 4);
|
||||
if (cullMode == CullMode.Landblock)
|
||||
cullMode = CullMode.None;
|
||||
SetCullMode(encoder, cullMode);
|
||||
pushConstants.DrawIdOffset = drawRange.FirstCommand;
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
encoder.MultiDrawIndexedIndirect(
|
||||
commandBuffer,
|
||||
commandBase + (uint)(drawRange.FirstCommand * sizeof(DrawElementsIndirectCommand)),
|
||||
(uint)drawRange.CommandCount,
|
||||
(uint)sizeof(DrawElementsIndirectCommand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BindPipelineWithMesh(
|
||||
|
|
@ -315,6 +425,25 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
(uint)byteCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds one category word for <c>mesh_detail.vert</c>'s statically used
|
||||
/// binding 9. EnvCell draws select their renderer-wide category via
|
||||
/// <c>uParamB=0</c>, so the value is semantically unused, but Vulkan still
|
||||
/// requires the declared descriptor to be valid.
|
||||
/// </summary>
|
||||
internal static void BindEnvironmentDetailCategory(
|
||||
IGpuPassEncoder encoder,
|
||||
IGpuFrame frame)
|
||||
{
|
||||
Span<uint> category = stackalloc uint[1];
|
||||
category[0] = 1u;
|
||||
BindRingSection<uint>(
|
||||
encoder,
|
||||
frame,
|
||||
GpuBindingModel.StorageInstanceDetailCategory,
|
||||
category);
|
||||
}
|
||||
|
||||
private void DisposeRhiResources()
|
||||
{
|
||||
_opaquePipeline?.Dispose();
|
||||
|
|
@ -323,5 +452,9 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
_alphaPipeline = null;
|
||||
_additivePipeline?.Dispose();
|
||||
_additivePipeline = null;
|
||||
_detailPipeline?.Dispose();
|
||||
_detailPipeline = null;
|
||||
_transparentDetailPipeline?.Dispose();
|
||||
_transparentDetailPipeline = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue