[GPU] Clamp a volume render-target view to the volume depth

Unreal binds a whole volume as a render target with the last slice set to
its depth, one past the end, and the resolver stopped the session on it.
The color block writes only the slices the volume has, so the view is now
clamped to them. A view that starts past the last slice is still fatal.
This commit is contained in:
kuba 2026-09-26 08:08:37 +02:00
parent 2d6afda90b
commit ecc904e06a
2 changed files with 28 additions and 2 deletions

View File

@ -289,9 +289,16 @@ public static partial class ImageRequestBuilders
var viewExtent = new Extent2D(Math.Max(width >> (int)words.MipLevel, 1), Math.Max(height >> (int)words.MipLevel, 1));
var viewDepth = Math.Max(depth >> (int)words.MipLevel, 1);
if (volume && (view.BaseLayer >= viewDepth || view.LayerCount > viewDepth - view.BaseLayer))
if (volume && view.BaseLayer < viewDepth && view.LayerCount > viewDepth - view.BaseLayer)
{
throw SubmissionScheduler.Fatal($"The 3D render-target view exceeds the mip depth: base={view.BaseLayer} count={view.LayerCount} depth={viewDepth} mip={words.MipLevel}.");
// Titles bind a whole volume with the last slice set to its depth, one past the end;
// the color block stops at the last slice of the mip.
view = view with { LayerCount = viewDepth - view.BaseLayer, ImageLayers = viewDepth };
}
if (volume && view.BaseLayer >= viewDepth)
{
throw SubmissionScheduler.Fatal($"The 3D render-target view starts past the mip depth: base={view.BaseLayer} count={view.LayerCount} depth={viewDepth} mip={words.MipLevel}.");
}
var description = ImageDescription.Create();

View File

@ -145,6 +145,25 @@ public sealed class ImageRequestBuildersTests : IClassFixture<HeadlessVulkanFixt
Assert.Equal(64u, resolution.Value.Request.Description.Extent.Depth);
}
[Fact]
public void ColorTarget_VolumeViewEndingOnePastTheLastSliceIsClamped()
{
var resolution = ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 32, 32, GuestTileMode.RenderTarget, sliceMax: 32, dimension: 2, depth: 31), 0xF, 0, false);
Assert.NotNull(resolution);
var request = resolution.Value.Request;
Assert.Equal(32u, request.Description.Extent.Depth);
Assert.Equal(0u, request.View.BaseLayer);
}
[Fact]
public void ColorTarget_VolumeViewStartingPastTheLastSliceIsFatal()
{
using var fatal = new FatalScope();
Assert.Throws<SchedulerFatalException>(() => ImageRequestBuilders.ColorTarget(
RegisterWords.Color(Base, 32, 32, GuestTileMode.RenderTarget, sliceStart: 32, sliceMax: 32, dimension: 2, depth: 31), 0xF, 0, false));
}
[Fact]
public void ColorTarget_RejectsUnsupportedRegisterStates()
{