refactor(lighting): extract GlobalLightPacker (shared binding=4 layout) — A7 Fix D prep
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ad53180190
commit
180b4af2a9
3 changed files with 105 additions and 32 deletions
|
|
@ -142,7 +142,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
private uint _globalLightsSsbo;
|
||||
private uint _instLightSetSsbo;
|
||||
private int[] _lightSetData = new int[256 * LightManager.MaxLightsPerObject];
|
||||
private float[] _globalLightData = new float[16 * 16]; // 16 floats (4 vec4) per GlobalLight
|
||||
private float[] _globalLightData = new float[GlobalLightPacker.FloatsPerLight * 16]; // 16 floats (4 vec4) per GlobalLight
|
||||
// This frame's point-light snapshot, handed in by GameWindow before Draw via
|
||||
// SetSceneLights. Null/empty ⇒ only ambient + sun render (all instance sets -1).
|
||||
private IReadOnlyList<LightSource>? _pointSnapshot;
|
||||
|
|
@ -1812,39 +1812,12 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
/// </summary>
|
||||
private unsafe void UploadGlobalLights()
|
||||
{
|
||||
var snap = _pointSnapshot;
|
||||
int n = snap?.Count ?? 0;
|
||||
int n = GlobalLightPacker.Pack(_pointSnapshot, ref _globalLightData);
|
||||
int count = n > 0 ? n : 1; // never zero-size
|
||||
int floatsNeeded = count * 16;
|
||||
if (_globalLightData.Length < floatsNeeded)
|
||||
_globalLightData = new float[floatsNeeded + 16 * 16];
|
||||
Array.Clear(_globalLightData, 0, floatsNeeded);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var L = snap![i];
|
||||
int o = i * 16;
|
||||
// posAndKind (xyz world pos, w kind)
|
||||
_globalLightData[o + 0] = L.WorldPosition.X;
|
||||
_globalLightData[o + 1] = L.WorldPosition.Y;
|
||||
_globalLightData[o + 2] = L.WorldPosition.Z;
|
||||
_globalLightData[o + 3] = (int)L.Kind;
|
||||
// dirAndRange (xyz forward, w range = Falloff×1.3)
|
||||
_globalLightData[o + 4] = L.WorldForward.X;
|
||||
_globalLightData[o + 5] = L.WorldForward.Y;
|
||||
_globalLightData[o + 6] = L.WorldForward.Z;
|
||||
_globalLightData[o + 7] = L.Range;
|
||||
// colorAndIntensity (xyz linear colour, w intensity)
|
||||
_globalLightData[o + 8] = L.ColorLinear.X;
|
||||
_globalLightData[o + 9] = L.ColorLinear.Y;
|
||||
_globalLightData[o + 10] = L.ColorLinear.Z;
|
||||
_globalLightData[o + 11] = L.Intensity;
|
||||
// coneAngleEtc (x cone radians; yzw reserved)
|
||||
_globalLightData[o + 12] = L.ConeAngle;
|
||||
}
|
||||
|
||||
// Pack guarantees _globalLightData holds at least max(n,1) * FloatsPerLight floats.
|
||||
fixed (float* gp = _globalLightData)
|
||||
UploadSsbo(_globalLightsSsbo, 4, gp, count * 16 * sizeof(float));
|
||||
UploadSsbo(_globalLightsSsbo, 4, gp,
|
||||
count * GlobalLightPacker.FloatsPerLight * sizeof(float));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
55
src/AcDream.Core/Lighting/GlobalLightPacker.cs
Normal file
55
src/AcDream.Core/Lighting/GlobalLightPacker.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AcDream.Core.Lighting;
|
||||
|
||||
/// <summary>
|
||||
/// Packs a point-light snapshot into the flat float layout the bindless mesh
|
||||
/// shader reads at SSBO binding=4 (<c>mesh_modern.vert</c> <c>GlobalLight gLights[]</c>):
|
||||
/// 16 floats (4 vec4) per light — posAndKind, dirAndRange, colorAndIntensity,
|
||||
/// coneAngleEtc. Pure (no GL), so both <c>WbDrawDispatcher</c> and
|
||||
/// <c>EnvCellRenderer</c> share ONE layout and cannot drift.
|
||||
/// </summary>
|
||||
public static class GlobalLightPacker
|
||||
{
|
||||
public const int FloatsPerLight = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Fill <paramref name="buffer"/> (grown + zero-cleared as needed) with the
|
||||
/// packed snapshot; returns the light count <c>n</c>. The buffer always has at
|
||||
/// least <see cref="FloatsPerLight"/> floats (so a zero-light frame still
|
||||
/// uploads a non-empty SSBO). Callers upload <c>max(n,1) * FloatsPerLight</c> floats.
|
||||
/// </summary>
|
||||
public static int Pack(IReadOnlyList<LightSource>? snapshot, ref float[] buffer)
|
||||
{
|
||||
int n = snapshot?.Count ?? 0;
|
||||
int floatsNeeded = Math.Max(n, 1) * FloatsPerLight;
|
||||
if (buffer.Length < floatsNeeded)
|
||||
buffer = new float[floatsNeeded + FloatsPerLight * 16];
|
||||
Array.Clear(buffer, 0, floatsNeeded);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var L = snapshot![i];
|
||||
int o = i * FloatsPerLight;
|
||||
// posAndKind (xyz world pos, w kind)
|
||||
buffer[o + 0] = L.WorldPosition.X;
|
||||
buffer[o + 1] = L.WorldPosition.Y;
|
||||
buffer[o + 2] = L.WorldPosition.Z;
|
||||
buffer[o + 3] = (int)L.Kind;
|
||||
// dirAndRange (xyz forward, w range)
|
||||
buffer[o + 4] = L.WorldForward.X;
|
||||
buffer[o + 5] = L.WorldForward.Y;
|
||||
buffer[o + 6] = L.WorldForward.Z;
|
||||
buffer[o + 7] = L.Range; // w = Range = Falloff × static_light_factor (1.3), pre-multiplied by LightInfoLoader — NOT the raw dat Falloff
|
||||
// colorAndIntensity (xyz linear colour, w intensity)
|
||||
buffer[o + 8] = L.ColorLinear.X;
|
||||
buffer[o + 9] = L.ColorLinear.Y;
|
||||
buffer[o + 10] = L.ColorLinear.Z;
|
||||
buffer[o + 11] = L.Intensity;
|
||||
// coneAngleEtc (x cone radians; yzw reserved)
|
||||
buffer[o + 12] = L.ConeAngle;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
45
tests/AcDream.Core.Tests/Lighting/GlobalLightPackerTests.cs
Normal file
45
tests/AcDream.Core.Tests/Lighting/GlobalLightPackerTests.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Lighting;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Lighting;
|
||||
|
||||
public class GlobalLightPackerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Pack_WritesSixteenFloatsPerLight_InTheExpectedLayout()
|
||||
{
|
||||
var light = new LightSource
|
||||
{
|
||||
Kind = LightKind.Point,
|
||||
WorldPosition = new Vector3(10f, 20f, 30f),
|
||||
WorldForward = new Vector3(0f, 0f, 1f),
|
||||
ColorLinear = new Vector3(1.0f, 0.588f, 0.314f),
|
||||
Intensity = 100f,
|
||||
Range = 5.2f,
|
||||
ConeAngle = 0f,
|
||||
};
|
||||
float[] buffer = System.Array.Empty<float>();
|
||||
|
||||
int count = GlobalLightPacker.Pack(new[] { light }, ref buffer);
|
||||
|
||||
Assert.Equal(1, count);
|
||||
Assert.True(buffer.Length >= 16);
|
||||
Assert.Equal(10f, buffer[0]); Assert.Equal(20f, buffer[1]); Assert.Equal(30f, buffer[2]);
|
||||
Assert.Equal((float)(int)LightKind.Point, buffer[3]);
|
||||
Assert.Equal(0f, buffer[4]); Assert.Equal(0f, buffer[5]); Assert.Equal(1f, buffer[6]);
|
||||
Assert.Equal(5.2f, buffer[7]);
|
||||
Assert.Equal(1.0f, buffer[8]); Assert.Equal(0.588f, buffer[9]); Assert.Equal(0.314f, buffer[10]);
|
||||
Assert.Equal(100f, buffer[11]);
|
||||
Assert.Equal(0f, buffer[12]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pack_NullOrEmpty_ReturnsZero_AndBufferHasAtLeastOneSlot()
|
||||
{
|
||||
float[] buffer = System.Array.Empty<float>();
|
||||
int count = GlobalLightPacker.Pack(null, ref buffer);
|
||||
Assert.Equal(0, count);
|
||||
Assert.True(buffer.Length >= GlobalLightPacker.FloatsPerLight);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue