Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
531 changes: 258 additions & 273 deletions hal/dx12/command.go

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions hal/dx12/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ func depthFormatToTypeless(format gputypes.TextureFormat) d3d12.DXGI_FORMAT {
return d3d12.DXGI_FORMAT_R32_TYPELESS
case gputypes.TextureFormatDepth32FloatStencil8:
return d3d12.DXGI_FORMAT_R32G8X24_TYPELESS
case gputypes.TextureFormatStencil8:
return d3d12.DXGI_FORMAT_R24G8_TYPELESS
default:
return d3d12.DXGI_FORMAT_UNKNOWN
}
Expand All @@ -258,6 +260,27 @@ func depthFormatToSRV(format gputypes.TextureFormat) d3d12.DXGI_FORMAT {
}
}

// textureFormatToSRV selects the typed view format and physical D3D12 plane
// for a WebGPU texture aspect. Packed depth/stencil formats expose their
// stencil plane through an X*TYPELESS_G8*UINT SRV. Standalone Stencil8 uses a
// D24S8 backing resource on DX12 and therefore follows the same representation.
func textureFormatToSRV(format gputypes.TextureFormat, aspect gputypes.TextureAspect) (d3d12.DXGI_FORMAT, uint32) {
if aspect == gputypes.TextureAspectStencilOnly || format == gputypes.TextureFormatStencil8 {
switch format {
case gputypes.TextureFormatDepth24PlusStencil8, gputypes.TextureFormatStencil8:
return d3d12.DXGI_FORMAT_X24_TYPELESS_G8_UINT, 1
case gputypes.TextureFormatDepth32FloatStencil8:
return d3d12.DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, 1
default:
return d3d12.DXGI_FORMAT_UNKNOWN, 1
}
}
if isDepthFormat(format) {
return depthFormatToSRV(format), 0
}
return textureFormatToD3D12(format), 0
}

// addressModeToD3D12 converts a WebGPU address mode to D3D12.
func addressModeToD3D12(mode gputypes.AddressMode) d3d12.D3D12_TEXTURE_ADDRESS_MODE {
switch mode {
Expand Down
25 changes: 25 additions & 0 deletions hal/dx12/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func TestDepthFormatToTypeless(t *testing.T) {
{"Depth24PlusStencil8", gputypes.TextureFormatDepth24PlusStencil8, d3d12.DXGI_FORMAT_R24G8_TYPELESS},
{"Depth32Float", gputypes.TextureFormatDepth32Float, d3d12.DXGI_FORMAT_R32_TYPELESS},
{"Depth32FloatStencil8", gputypes.TextureFormatDepth32FloatStencil8, d3d12.DXGI_FORMAT_R32G8X24_TYPELESS},
{"Stencil8", gputypes.TextureFormatStencil8, d3d12.DXGI_FORMAT_R24G8_TYPELESS},
{"Non-depth returns UNKNOWN", gputypes.TextureFormatRGBA8Unorm, d3d12.DXGI_FORMAT_UNKNOWN},
}

Expand All @@ -249,6 +250,30 @@ func TestDepthFormatToTypeless(t *testing.T) {
}
}

func TestTextureFormatToSRVSelectsAspectPlane(t *testing.T) {
tests := []struct {
name string
format gputypes.TextureFormat
aspect gputypes.TextureAspect
wantFormat d3d12.DXGI_FORMAT
wantPlane uint32
}{
{"D24S8 depth", gputypes.TextureFormatDepth24PlusStencil8, gputypes.TextureAspectDepthOnly, d3d12.DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 0},
{"D24S8 stencil", gputypes.TextureFormatDepth24PlusStencil8, gputypes.TextureAspectStencilOnly, d3d12.DXGI_FORMAT_X24_TYPELESS_G8_UINT, 1},
{"D32S8 stencil", gputypes.TextureFormatDepth32FloatStencil8, gputypes.TextureAspectStencilOnly, d3d12.DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, 1},
{"standalone stencil defaults to stencil plane", gputypes.TextureFormatStencil8, gputypes.TextureAspectAll, d3d12.DXGI_FORMAT_X24_TYPELESS_G8_UINT, 1},
{"color", gputypes.TextureFormatRGBA8Unorm, gputypes.TextureAspectAll, d3d12.DXGI_FORMAT_R8G8B8A8_UNORM, 0},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
format, plane := textureFormatToSRV(test.format, test.aspect)
if format != test.wantFormat || plane != test.wantPlane {
t.Fatalf("SRV selection = (%d, %d), want (%d, %d)", format, plane, test.wantFormat, test.wantPlane)
}
})
}
}

func TestDepthFormatToSRV(t *testing.T) {
tests := []struct {
name string
Expand Down
123 changes: 123 additions & 0 deletions hal/dx12/copy_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2025 The GoGPU Authors
// SPDX-License-Identifier: MIT

//go:build windows && !(js && wasm)

package dx12

import (
"github.com/gogpu/gputypes"
"github.com/gogpu/wgpu/hal"
"github.com/gogpu/wgpu/hal/dx12/d3d12"
)

func (e *CommandEncoder) copyBufferToTexture(src *Buffer, texture *Texture, regions []hal.BufferTextureCopy) {
plans := make([]stateBarrierPlan, 0, 1+len(regions))
if before, barrier := e.stateTracker.transitionBuffer(src, d3d12.D3D12_RESOURCE_STATE_COPY_SOURCE); barrier {
plans = append(plans, stateBarrierPlan{resource: src, subresource: d3d12.D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, before: before, after: d3d12.D3D12_RESOURCE_STATE_COPY_SOURCE})
}
for _, region := range regions {
for _, copyPlan := range planBufferTextureCopies(texture, region.TextureBase, region.BufferLayout, region.Size) {
if before, barrier := e.stateTracker.transitionTexture(texture, copyPlan.subresource, d3d12.D3D12_RESOURCE_STATE_COPY_DEST); barrier {
plans = append(plans, stateBarrierPlan{resource: texture, subresource: copyPlan.subresource, before: before, after: d3d12.D3D12_RESOURCE_STATE_COPY_DEST})
}
}
}
e.emitStateBarrierPlans(plans)
for _, region := range regions {
for _, plan := range planBufferTextureCopies(texture, region.TextureBase, region.BufferLayout, region.Size) {
srcLoc := placedFootprintLocation(src, texture.format, plan)
dstLoc := subresourceLocation(texture, plan.subresource)
box := d3d12.D3D12_BOX{
Left: plan.bufferOriginX,
Top: plan.bufferOriginY,
Front: 0,
Right: plan.bufferOriginX + plan.copyWidth,
Bottom: plan.bufferOriginY + plan.copyHeight,
Back: plan.footprintDepth,
}
e.cmdList.CopyTextureRegion(&dstLoc, plan.textureOriginX, plan.textureOriginY, plan.textureOriginZ, &srcLoc, &box)
}
}
}

func (e *CommandEncoder) copyTextureToBuffer(src *Texture, dst *Buffer, regions []hal.BufferTextureCopy) {
plans := make([]stateBarrierPlan, 0, 1+len(regions))
if before, barrier := e.stateTracker.transitionBuffer(dst, d3d12.D3D12_RESOURCE_STATE_COPY_DEST); barrier {
plans = append(plans, stateBarrierPlan{resource: dst, subresource: d3d12.D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, before: before, after: d3d12.D3D12_RESOURCE_STATE_COPY_DEST})
}
for _, region := range regions {
for _, copyPlan := range planBufferTextureCopies(src, region.TextureBase, region.BufferLayout, region.Size) {
if before, barrier := e.stateTracker.transitionTexture(src, copyPlan.subresource, d3d12.D3D12_RESOURCE_STATE_COPY_SOURCE); barrier {
plans = append(plans, stateBarrierPlan{resource: src, subresource: copyPlan.subresource, before: before, after: d3d12.D3D12_RESOURCE_STATE_COPY_SOURCE})
}
}
}
e.emitStateBarrierPlans(plans)
for _, region := range regions {
for _, plan := range planBufferTextureCopies(src, region.TextureBase, region.BufferLayout, region.Size) {
srcLoc := subresourceLocation(src, plan.subresource)
dstLoc := placedFootprintLocation(dst, src.format, plan)
box := d3d12.D3D12_BOX{
Left: plan.textureOriginX,
Top: plan.textureOriginY,
Front: plan.textureOriginZ,
Right: plan.textureOriginX + plan.copyWidth,
Bottom: plan.textureOriginY + plan.copyHeight,
Back: plan.textureOriginZ + plan.footprintDepth,
}
e.cmdList.CopyTextureRegion(&dstLoc, plan.bufferOriginX, plan.bufferOriginY, 0, &srcLoc, &box)
}
}
}

func (e *CommandEncoder) copyTextureToTexture(src, dst *Texture, regions []hal.TextureCopy) {
plans := make([]stateBarrierPlan, 0, len(regions)*2)
for _, region := range regions {
for _, copyPlan := range planTextureTextureCopies(src, dst, region) {
if before, barrier := e.stateTracker.transitionTexture(src, copyPlan.srcSubresource, d3d12.D3D12_RESOURCE_STATE_COPY_SOURCE); barrier {
plans = append(plans, stateBarrierPlan{resource: src, subresource: copyPlan.srcSubresource, before: before, after: d3d12.D3D12_RESOURCE_STATE_COPY_SOURCE})
}
if before, barrier := e.stateTracker.transitionTexture(dst, copyPlan.dstSubresource, d3d12.D3D12_RESOURCE_STATE_COPY_DEST); barrier {
plans = append(plans, stateBarrierPlan{resource: dst, subresource: copyPlan.dstSubresource, before: before, after: d3d12.D3D12_RESOURCE_STATE_COPY_DEST})
}
}
}
e.emitStateBarrierPlans(plans)
for _, region := range regions {
for _, plan := range planTextureTextureCopies(src, dst, region) {
srcLoc := subresourceLocation(src, plan.srcSubresource)
dstLoc := subresourceLocation(dst, plan.dstSubresource)
box := d3d12.D3D12_BOX{
Left: region.SrcBase.Origin.X,
Top: region.SrcBase.Origin.Y,
Front: plan.srcFront,
Right: region.SrcBase.Origin.X + region.Size.Width,
Bottom: region.SrcBase.Origin.Y + region.Size.Height,
Back: plan.srcBack,
}
e.cmdList.CopyTextureRegion(&dstLoc, region.DstBase.Origin.X, region.DstBase.Origin.Y, plan.dstZ, &srcLoc, &box)
}
}
}

func placedFootprintLocation(buffer *Buffer, format gputypes.TextureFormat, plan bufferTextureCopyPlan) d3d12.D3D12_TEXTURE_COPY_LOCATION {
location := d3d12.D3D12_TEXTURE_COPY_LOCATION{Resource: buffer.raw, Type: d3d12.D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT}
location.SetPlacedFootprint(d3d12.D3D12_PLACED_SUBRESOURCE_FOOTPRINT{
Offset: plan.bufferOffset,
Footprint: d3d12.D3D12_SUBRESOURCE_FOOTPRINT{
Format: textureFormatToD3D12(format),
Width: plan.footprintWidth,
Height: plan.footprintHeight,
Depth: plan.footprintDepth,
RowPitch: plan.rowPitch,
},
})
return location
}

func subresourceLocation(texture *Texture, subresource uint32) d3d12.D3D12_TEXTURE_COPY_LOCATION {
location := d3d12.D3D12_TEXTURE_COPY_LOCATION{Resource: texture.raw, Type: d3d12.D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX}
location.SetSubresourceIndex(subresource)
return location
}
Loading