D3D11: Implement B2T using shaders. Bug: 348653642 Change-Id: I00d27e9e45b3c85b1176ae647e10e4c740bca90b Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/230419 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Quyen Le <lehoangquyen@chromium.org> Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn index 605e881..0ba75d4 100644 --- a/src/dawn/native/BUILD.gn +++ b/src/dawn/native/BUILD.gn
@@ -221,6 +221,8 @@ "BindingInfo.h", "BlitBufferToDepthStencil.cpp", "BlitBufferToDepthStencil.h", + "BlitBufferToTexture.cpp", + "BlitBufferToTexture.h", "BlitColorToColorWithDraw.cpp", "BlitColorToColorWithDraw.h", "BlitDepthToDepth.cpp",
diff --git a/src/dawn/native/BlitBufferToTexture.cpp b/src/dawn/native/BlitBufferToTexture.cpp new file mode 100644 index 0000000..805690d --- /dev/null +++ b/src/dawn/native/BlitBufferToTexture.cpp
@@ -0,0 +1,449 @@ +// Copyright 2025 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "dawn/native/BlitBufferToTexture.h" + +#include <sstream> +#include <string> +#include <string_view> +#include <utility> + +#include "dawn/common/Assert.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/Device.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/Queue.h" +#include "dawn/native/RenderPassEncoder.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/utils/WGPUHelpers.h" + +namespace dawn::native { + +namespace { + +constexpr std::string_view kShaderCommonSrc = R"( +@vertex fn vert_fullscreen_quad( + @builtin(vertex_index) vertex_index : u32 +) -> @builtin(position) vec4f { + const pos = array( + vec2f(-1.0, -1.0), + vec2f( 3.0, -1.0), + vec2f(-1.0, 3.0)); + return vec4f(pos[vertex_index], 0.0, 1.0); +} + +struct Params { + srcOffset : u32, + bytesPerRow : u32, + dstOrigin : vec2u +}; + +@group(0) @binding(0) var<storage, read> src_buf : array<u32>; +@group(0) @binding(1) var<uniform> params : Params; + +fn loadU8AsU32(byteOffset: u32) -> u32 { + let uintOffset = byteOffset >> 2; + let uintModOffset = byteOffset & 3; + let bitShift = uintModOffset * 8; + return (src_buf[uintOffset] >> bitShift) & 0xff; +} + +fn loadU16AsU32(byteOffset: u32) -> u32 { + let firstHalf = loadU8AsU32(byteOffset); + let secondHalf = loadU8AsU32(byteOffset + 1); + return firstHalf | (secondHalf << 8); +} + +// byteOffset is expected to be aligned to 4. +fn loadU32(byteOffset: u32) -> u32 { + let uintOffset = byteOffset >> 2; + return src_buf[uintOffset]; +} + +// byteOffset is expected to be aligned to 4. +fn loadTwoU32s(byteOffset: u32) -> vec2u { + return vec2u(loadU32(byteOffset), loadU32(byteOffset + 4)); +} + +@fragment fn blit_buffer_to_texture( + @builtin(position) screen_position : vec4f +) -> @location(0) vec4f { + let iposition = vec2u(screen_position.xy) - params.dstOrigin; + + let srcOffset = params.srcOffset + iposition.x * kPixelSize + iposition.y * params.bytesPerRow; + + return unpackData(srcOffset); +} +)"; + +constexpr std::string_view kUnpackR8Unorm = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return unpack4x8unorm(loadU8AsU32(byteOffset)); +} +)"; + +constexpr std::string_view kUnpackRG8Unorm = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return unpack4x8unorm(loadU16AsU32(byteOffset)); +} +)"; + +constexpr std::string_view kUnpackRGBA8Unorm = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return unpack4x8unorm(loadU32(byteOffset)); +} +)"; + +constexpr std::string_view kUnpackBGRA8Unorm = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return unpack4x8unorm(loadU32(byteOffset)).bgra; +} +)"; + +constexpr std::string_view kUnpackR16Float = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return vec4f(unpack2x16float(loadU16AsU32(byteOffset)), 0.0, 1.0); +} +)"; + +constexpr std::string_view kUnpackRG16Float = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return vec4f(unpack2x16float(loadU32(byteOffset)), 0.0, 1.0); +} +)"; + +constexpr std::string_view kUnpackRGBA16Float = R"( +fn unpackData(byteOffset: u32) -> vec4f { + let data = loadTwoU32s(byteOffset); + return vec4f(unpack2x16float(data.x), unpack2x16float(data.y)); +} +)"; + +constexpr std::string_view kUnpackR32Float = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return vec4f(bitcast<f32>(loadU32(byteOffset)), 0.0, 0.0, 1.0); +} +)"; + +constexpr std::string_view kUnpackRG32Float = R"( +fn unpackData(byteOffset: u32) -> vec4f { + let color = bitcast<vec2f>(loadTwoU32s(byteOffset)); + return vec4f(color, 0.0, 1.0); +} +)"; + +constexpr std::string_view kUnpackRGBA32Float = R"( +fn unpackData(byteOffset: u32) -> vec4f { + return vec4f(bitcast<vec2f>(loadTwoU32s(byteOffset)), + bitcast<vec2f>(loadTwoU32s(byteOffset + 8))); +} +)"; + +std::string GenerateShaderSource(wgpu::TextureFormat format) { + int pixelSize = 0; + std::ostringstream ss; + switch (format) { + case wgpu::TextureFormat::R8Unorm: + pixelSize = 1; + ss << kUnpackR8Unorm; + break; + case wgpu::TextureFormat::RG8Unorm: + pixelSize = 2; + ss << kUnpackRG8Unorm; + break; + case wgpu::TextureFormat::RGBA8Unorm: + pixelSize = 4; + ss << kUnpackRGBA8Unorm; + break; + case wgpu::TextureFormat::BGRA8Unorm: + pixelSize = 4; + ss << kUnpackBGRA8Unorm; + break; + case wgpu::TextureFormat::R16Float: + pixelSize = 2; + ss << kUnpackR16Float; + break; + case wgpu::TextureFormat::RG16Float: + pixelSize = 4; + ss << kUnpackRG16Float; + break; + case wgpu::TextureFormat::RGBA16Float: + pixelSize = 8; + ss << kUnpackRGBA16Float; + break; + case wgpu::TextureFormat::R32Float: + pixelSize = 4; + ss << kUnpackR32Float; + break; + case wgpu::TextureFormat::RG32Float: + pixelSize = 8; + ss << kUnpackRG32Float; + break; + case wgpu::TextureFormat::RGBA32Float: + pixelSize = 16; + ss << kUnpackRGBA32Float; + break; + default: + DAWN_UNREACHABLE(); + } + + ss << "const kPixelSize = " << pixelSize << ";\n"; + ss << kShaderCommonSrc; + + return ss.str(); +} + +ResultOrError<Ref<RenderPipelineBase>> GetOrCreatePipeline(DeviceBase* device, + wgpu::TextureFormat format) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + { + auto it = store->blitBufferToTexturePipelines.find(format); + if (it != store->blitBufferToTexturePipelines.end()) { + return it->second; + } + } + + // vertex shader's source. + ShaderSourceWGSL wgslDesc = {}; + ShaderModuleDescriptor shaderModuleDesc = {}; + shaderModuleDesc.nextInChain = &wgslDesc; + + // shader's source will depend on format key. + std::string shaderCode = GenerateShaderSource(format); + wgslDesc.code = shaderCode.c_str(); + Ref<ShaderModuleBase> shaderModule; + DAWN_TRY_ASSIGN(shaderModule, device->CreateShaderModule(&shaderModuleDesc)); + + FragmentState fragmentState = {}; + fragmentState.module = shaderModule.Get(); + fragmentState.entryPoint = "blit_buffer_to_texture"; + + // Color target states. + ColorTargetState colorTarget = {}; + colorTarget.format = format; + colorTarget.writeMask = wgpu::ColorWriteMask::All; + + fragmentState.targetCount = 1; + fragmentState.targets = &colorTarget; + + RenderPipelineDescriptor renderPipelineDesc = {}; + renderPipelineDesc.label = "blit_buffer_to_texture"; + renderPipelineDesc.vertex.module = shaderModule.Get(); + renderPipelineDesc.vertex.entryPoint = "vert_fullscreen_quad"; + renderPipelineDesc.fragment = &fragmentState; + + // Bind group layout. + Ref<BindGroupLayoutBase> bindGroupLayout; + DAWN_TRY_ASSIGN(bindGroupLayout, + utils::MakeBindGroupLayout( + device, + { + {0, wgpu::ShaderStage::Fragment, kInternalReadOnlyStorageBufferBinding}, + {1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}, + }, + /* allowInternalBinding */ true)); + + Ref<PipelineLayoutBase> pipelineLayout; + DAWN_TRY_ASSIGN(pipelineLayout, utils::MakeBasicPipelineLayout(device, bindGroupLayout)); + renderPipelineDesc.layout = pipelineLayout.Get(); + + Ref<RenderPipelineBase> pipeline; + DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc)); + + store->blitBufferToTexturePipelines.emplace(format, pipeline); + return pipeline; +} + +} // anonymous namespace + +bool IsFormatSupportedByBufferToTextureBlit(wgpu::TextureFormat format) { + // TODO(348653642): Eventually we should support all non-compressed formats. For now, just list + // a subset of them that we support. + switch (format) { + case wgpu::TextureFormat::R8Unorm: + case wgpu::TextureFormat::RG8Unorm: + case wgpu::TextureFormat::RGBA8Unorm: + case wgpu::TextureFormat::BGRA8Unorm: + case wgpu::TextureFormat::R16Float: + case wgpu::TextureFormat::RG16Float: + case wgpu::TextureFormat::RGBA16Float: + case wgpu::TextureFormat::R32Float: + case wgpu::TextureFormat::RG32Float: + case wgpu::TextureFormat::RGBA32Float: + return true; + default: + return false; + } +} + +bool IsBufferToTextureBlitSupported(BufferBase* buffer, + const TextureCopy& dst, + const Extent3D& copyExtent) { + if (!(buffer->GetInternalUsage() & + (kReadOnlyStorageBuffer | kInternalStorageBuffer | wgpu::BufferUsage::Storage))) { + return false; + } + + if (!IsFormatSupportedByBufferToTextureBlit(dst.texture->GetFormat().format)) { + return false; + } + + if (dst.texture->GetDimension() == wgpu::TextureDimension::e1D) { + // 1D texture cannot be rendered to so skip it. + return false; + } + + if (dst.aspect != Aspect::Color) { + // Don't support multiplanar copies yet. + return false; + } + + // Must have non-zero copy size. + return copyExtent.width * copyExtent.height * copyExtent.depthOrArrayLayers > 0; +} + +MaybeError BlitBufferToTexture(DeviceBase* device, + CommandEncoder* commandEncoder, + BufferBase* buffer, + const TexelCopyBufferLayout& src, + const TextureCopy& dst, + const Extent3D& copyExtent) { + DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded()); + + // This function assumes bytesPerRow is multiples of 4. Normally it's required that + // bytesPerRow is aligned to 256. However some backends might enable + // DawnTexelCopyBufferRowAlignment feature to relax the alignment. Currently only D3D11 backend + // enables this feature, and the relaxed alignment there is 4. + DAWN_ASSERT((src.bytesPerRow % 4) == 0); + + DAWN_ASSERT(buffer->GetInternalUsage() & + (kReadOnlyStorageBuffer | kInternalStorageBuffer | wgpu::BufferUsage::Storage)); + + DAWN_ASSERT(copyExtent.width > 0 && copyExtent.height > 0 && copyExtent.depthOrArrayLayers > 0); + + // Allow internal usages since we need to use the destination + // as a render attachment. + auto scope = commandEncoder->MakeInternalUsageScope(); + + Ref<RenderPipelineBase> pipeline; + DAWN_TRY_ASSIGN(pipeline, GetOrCreatePipeline(device, dst.texture->GetFormat().format)); + + Ref<BindGroupLayoutBase> bgl; + DAWN_TRY_ASSIGN(bgl, pipeline->GetBindGroupLayout(0)); + + const auto ssboAlignment = device->GetLimits().v1.minStorageBufferOffsetAlignment; + DAWN_ASSERT(IsPowerOfTwo(ssboAlignment)); + + wgpu::TextureViewDimension viewDimension; + uint32_t baseDepth = 0; + uint32_t baseArray = 0; + uint32_t depthStep = 0; + uint32_t arrayStep = 0; + switch (dst.texture->GetDimension()) { + case wgpu::TextureDimension::e1D: + DAWN_UNREACHABLE(); + break; + case wgpu::TextureDimension::e3D: + viewDimension = wgpu::TextureViewDimension::e3D; + baseDepth = dst.origin.z; + depthStep = 1; + break; + default: + viewDimension = wgpu::TextureViewDimension::e2D; + baseArray = dst.origin.z; + arrayStep = 1; + break; + } + + for (uint32_t z = 0; z < copyExtent.depthOrArrayLayers; ++z) { + Ref<TextureViewBase> dstView; + { + TextureViewDescriptor viewDesc = {}; + viewDesc.dimension = viewDimension; + viewDesc.baseArrayLayer = baseArray + arrayStep * z; + viewDesc.arrayLayerCount = 1; + viewDesc.baseMipLevel = dst.mipLevel; + viewDesc.mipLevelCount = 1; + DAWN_TRY_ASSIGN(dstView, dst.texture->CreateView(&viewDesc)); + } + + const uint64_t srcOffset = src.offset + z * src.rowsPerImage * src.bytesPerRow; + const uint64_t srcBufferBindingOffset = AlignDown(srcOffset, ssboAlignment); + const uint32_t shaderReadOffset = static_cast<uint32_t>(srcOffset & (ssboAlignment - 1)); + Ref<BufferBase> paramsBuffer; + { + DAWN_TRY_ASSIGN(paramsBuffer, + device->GetOrCreateTemporaryUniformBuffer(sizeof(uint32_t) * 4)); + + uint32_t params[4]; + params[0] = shaderReadOffset; + params[1] = src.bytesPerRow; + params[2] = dst.origin.x; + params[3] = dst.origin.y; + commandEncoder->APIWriteBuffer(paramsBuffer.Get(), 0, + reinterpret_cast<const uint8_t*>(¶ms[0]), + sizeof(params)); + } + + Ref<BindGroupBase> bindGroup; + DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup(device, bgl, + { + {0, buffer, srcBufferBindingOffset}, + {1, paramsBuffer}, + }, + UsageValidationMode::Internal)); + + RenderPassColorAttachment colorAttachment; + colorAttachment.view = dstView.Get(); + if (depthStep) { + colorAttachment.depthSlice = baseDepth + depthStep * z; + } + colorAttachment.loadOp = wgpu::LoadOp::Load; + colorAttachment.storeOp = wgpu::StoreOp::Store; + + RenderPassDescriptor rpDesc = {}; + rpDesc.colorAttachmentCount = 1; + rpDesc.colorAttachments = &colorAttachment; + + Ref<RenderPassEncoder> pass = commandEncoder->BeginRenderPass(&rpDesc); + // Bind the resources. + pass->APISetBindGroup(0, bindGroup.Get()); + pass->APISetViewport(dst.origin.x, dst.origin.y, copyExtent.width, copyExtent.height, 0.f, + 1.f); + + // Draw to perform the blit. + pass->APISetPipeline(pipeline.Get()); + pass->APIDraw(3, 1, 0, 0); + + pass->End(); + } + return {}; +} + +} // namespace dawn::native
diff --git a/src/dawn/native/BlitBufferToTexture.h b/src/dawn/native/BlitBufferToTexture.h new file mode 100644 index 0000000..20497a5 --- /dev/null +++ b/src/dawn/native/BlitBufferToTexture.h
@@ -0,0 +1,53 @@ +// Copyright 2025 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_DAWN_NATIVE_BLITBUFFERTOTEXTURE_H_ +#define SRC_DAWN_NATIVE_BLITBUFFERTOTEXTURE_H_ + +#include "dawn/native/Error.h" + +namespace dawn::native { + +struct Format; +struct TextureCopy; +struct TexelCopyBufferLayout; + +bool IsFormatSupportedByBufferToTextureBlit(wgpu::TextureFormat format); +bool IsBufferToTextureBlitSupported(BufferBase* buffer, + const TextureCopy& dst, + const Extent3D& copyExtent); + +MaybeError BlitBufferToTexture(DeviceBase* device, + CommandEncoder* commandEncoder, + BufferBase* buffer, + const TexelCopyBufferLayout& src, + const TextureCopy& dst, + const Extent3D& copyExtent); + +} // namespace dawn::native + +#endif // SRC_DAWN_NATIVE_BLITBUFFERTOTEXTURE_H_
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp index 77b5d39..7e3397f 100644 --- a/src/dawn/native/Buffer.cpp +++ b/src/dawn/native/Buffer.cpp
@@ -97,7 +97,14 @@ std::unique_ptr<uint8_t[]> mFakeMappedData; }; -wgpu::BufferUsage AddInternalUsages(const DeviceBase* device, wgpu::BufferUsage usage) { +// GetMappedRange on a zero-sized buffer returns a pointer to this value. +static uint32_t sZeroSizedMappingData = 0xCAFED00D; + +} // anonymous namespace + +wgpu::BufferUsage ComputeInternalBufferUsages(const DeviceBase* device, + wgpu::BufferUsage usage, + size_t bufferSize) { // Add readonly storage usage if the buffer has a storage usage. The validation rules in // ValidateSyncScopeResourceUsage will make sure we don't use both at the same time. if (usage & wgpu::BufferUsage::Storage) { @@ -135,11 +142,10 @@ device->IsToggleEnabled(Toggle::UseBlitForFloat32TextureCopy) || device->IsToggleEnabled(Toggle::UseBlitForT2B); if (useComputeForT2B) { - if (!(usage & (kMappableBufferUsages | wgpu::BufferUsage::Uniform)) || - !device->PreferNotUsingMappableOrUniformBufferAsStorage()) { - // If buffer doesn't have mapping nor Uniform usage, or backend is ok with using - // this kind of buffer as storage buffer, we can add Storage usage in order to write - // to it in compute shader. + if (device->CanAddStorageUsageToBufferWithoutSideEffects(kInternalStorageBuffer, usage, + bufferSize)) { + // If the backend is ok with using this kind of buffer as storage buffer, we can add + // Storage usage in order to write to it in compute shader. usage |= kInternalStorageBuffer; } @@ -151,14 +157,18 @@ } } + if ((usage & wgpu::BufferUsage::CopySrc) && device->IsToggleEnabled(Toggle::UseBlitForB2T)) { + if (device->CanAddStorageUsageToBufferWithoutSideEffects(kReadOnlyStorageBuffer, usage, + bufferSize)) { + // If the backend is ok with using this kind of buffer as readonly storage buffer, + // we can add Storage usage in order to read from it in pixel shader. + usage |= kReadOnlyStorageBuffer; + } + } + return usage; } -// GetMappedRange on a zero-sized buffer returns a pointer to this value. -static uint32_t sZeroSizedMappingData = 0xCAFED00D; - -} // anonymous namespace - struct BufferBase::MapAsyncEvent final : public EventManager::TrackedEvent { // MapAsyncEvent stores a raw pointer to the buffer so that it can update the buffer's map state // when it completes. If the map completes early (error, unmap, destroy), then the buffer is no @@ -339,7 +349,7 @@ : SharedResource(device, descriptor->label), mSize(descriptor->size), mUsage(descriptor->usage), - mInternalUsage(AddInternalUsages(device, descriptor->usage)), + mInternalUsage(ComputeInternalBufferUsages(device, descriptor->usage, descriptor->size)), mState(descriptor.Get<BufferHostMappedPointer>() ? BufferState::HostMappedPersistent : BufferState::Unmapped) { GetObjectTrackingList()->Track(this);
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h index da80095..d0439eb 100644 --- a/src/dawn/native/Buffer.h +++ b/src/dawn/native/Buffer.h
@@ -69,6 +69,12 @@ static constexpr wgpu::BufferUsage kReadOnlyShaderBufferUsages = kShaderBufferUsages & kReadOnlyBufferUsages; +// Return the actual internal buffer usages that will be used to create a buffer. +// In other words, after being created, buffer.GetInternalUsage() will return this value. +wgpu::BufferUsage ComputeInternalBufferUsages(const DeviceBase* device, + wgpu::BufferUsage usage, + size_t bufferSize); + class BufferBase : public SharedResource { public: enum class BufferState {
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt index 155a0f9..d3128bf 100644 --- a/src/dawn/native/CMakeLists.txt +++ b/src/dawn/native/CMakeLists.txt
@@ -50,6 +50,7 @@ "BindGroupTracker.h" "BindingInfo.h" "BlitBufferToDepthStencil.h" + "BlitBufferToTexture.h" "BlitColorToColorWithDraw.h" "BlitDepthToDepth.h" "BlitTextureToBuffer.h" @@ -167,6 +168,7 @@ "BindGroupLayoutInternal.cpp" "BindingInfo.cpp" "BlitBufferToDepthStencil.cpp" + "BlitBufferToTexture.cpp" "BlitColorToColorWithDraw.cpp" "BlitDepthToDepth.cpp" "BlitTextureToBuffer.cpp"
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp index c3e8fe8..f89de32 100644 --- a/src/dawn/native/CommandEncoder.cpp +++ b/src/dawn/native/CommandEncoder.cpp
@@ -40,6 +40,7 @@ #include "dawn/native/ApplyClearColorValueWithDrawHelper.h" #include "dawn/native/BindGroup.h" #include "dawn/native/BlitBufferToDepthStencil.h" +#include "dawn/native/BlitBufferToTexture.h" #include "dawn/native/BlitDepthToDepth.h" #include "dawn/native/BlitTextureToBuffer.h" #include "dawn/native/Buffer.h" @@ -1617,6 +1618,18 @@ "copying from %s to stencil aspect of %s using blit workaround.", source->buffer, dst.texture.Get()); return {}; + } else if (GetDevice()->IsToggleEnabled(Toggle::UseBlitForB2T) && + IsBufferToTextureBlitSupported(source->buffer, dst, *copySize)) { + // This function might create new resources. Need to lock the Device. + // TODO(crbug.com/dawn/1618): In future, all temp resources should be created at + // Command Submit time, so the locking would be removed from here at that point. + auto deviceLock(GetDevice()->GetScopedLock()); + DAWN_TRY_CONTEXT(BlitBufferToTexture(GetDevice(), this, source->buffer, srcLayout, + dst, *copySize), + "copying buffer %s to %s using blit workaround.", source->buffer, + dst.texture.Get()); + + return {}; } CopyBufferToTextureCmd* copy =
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp index 0038ee4..d79a65e 100644 --- a/src/dawn/native/Device.cpp +++ b/src/dawn/native/Device.cpp
@@ -402,6 +402,11 @@ // Handle maxXXXPerStage/maxXXXInStage. EnforceLimitSpecInvariants(&mLimits.v1, effectiveFeatureLevel); + if (mLimits.v1.maxStorageBuffersInFragmentStage < 1) { + // If there is no storage buffer in fragment stage, UseBlitForB2T is not possible. + mToggles.ForceSet(Toggle::UseBlitForB2T, false); + } + mFormatTable = BuildFormatTable(this); if (!descriptor->label.IsUndefined()) { @@ -2367,8 +2372,10 @@ return false; } -bool DeviceBase::PreferNotUsingMappableOrUniformBufferAsStorage() const { - return false; +bool DeviceBase::CanAddStorageUsageToBufferWithoutSideEffects(wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) const { + return true; } uint64_t DeviceBase::GetBufferCopyOffsetAlignmentForDepthStencil() const {
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h index 6212db9..5b17855 100644 --- a/src/dawn/native/Device.h +++ b/src/dawn/native/Device.h
@@ -257,7 +257,7 @@ SamplerBase* APICreateSampler(const SamplerDescriptor* descriptor); ShaderModuleBase* APICreateShaderModule(const ShaderModuleDescriptor* descriptor); ShaderModuleBase* APICreateErrorShaderModule(const ShaderModuleDescriptor* descriptor, - StringView errorMessage); + StringView errorMessage); TextureBase* APICreateTexture(const TextureDescriptor* descriptor); InternalPipelineStore* GetInternalPipelineStore(); @@ -379,8 +379,12 @@ // will be resolved into. virtual bool CanTextureLoadResolveTargetInTheSameRenderpass() const; - // Whether the backend prefer not using mappable/uniform buffer as storage buffer. - virtual bool PreferNotUsingMappableOrUniformBufferAsStorage() const; + // Whether the backend can add internal storage usage to the buffer without side effects. + // - storageUsage is the internal storage usage that would be added. + // - originalUsage is the original usage of the buffer. + virtual bool CanAddStorageUsageToBufferWithoutSideEffects(wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) const; bool HasFeature(Feature feature) const;
diff --git a/src/dawn/native/InternalPipelineStore.h b/src/dawn/native/InternalPipelineStore.h index 6d53a83..cce4aac 100644 --- a/src/dawn/native/InternalPipelineStore.h +++ b/src/dawn/native/InternalPipelineStore.h
@@ -110,6 +110,7 @@ blitR8ToStencilPipelines; absl::flat_hash_map<wgpu::TextureFormat, Ref<RenderPipelineBase>> depthBlitPipelines; + absl::flat_hash_map<wgpu::TextureFormat, Ref<RenderPipelineBase>> blitBufferToTexturePipelines; BlitColorToColorWithDrawPipelinesCache expandResolveTexturePipelines;
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp index fc2ee35..e395ab4 100644 --- a/src/dawn/native/Texture.cpp +++ b/src/dawn/native/Texture.cpp
@@ -35,6 +35,7 @@ #include "dawn/common/Constants.h" #include "dawn/common/Math.h" #include "dawn/native/Adapter.h" +#include "dawn/native/BlitBufferToTexture.h" #include "dawn/native/BlitTextureToBuffer.h" #include "dawn/native/ChainUtils.h" #include "dawn/native/CommandValidation.h" @@ -524,6 +525,11 @@ device->IsToggleEnabled(Toggle::UseBlitForBufferToStencilTextureCopy)) { return true; } + + if (device->IsToggleEnabled(Toggle::UseBlitForB2T) && + IsFormatSupportedByBufferToTextureBlit(format.format)) { + return true; + } return false; }
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp index e771ea0..bcc05a4 100644 --- a/src/dawn/native/Toggles.cpp +++ b/src/dawn/native/Toggles.cpp
@@ -449,6 +449,15 @@ "Use a compute based blit instead of a copy command to copy texture with supported format to " "a buffer.", "https://crbug.com/dawn/348654098", ToggleStage::Device}}, + {Toggle::UseBlitForB2T, + {"use_blit_for_b2t", + "Use a shader based blit instead of a copy command to copy a buffer to a texture with " + "supported format.", + "https://crbug.com/dawn/348653642", ToggleStage::Device}}, + {Toggle::D3D11DisableCPUUploadBuffers, + {"d3d11_disable_cpu_buffers", + "Force disabling the usages of CPU upload buffers in the D3D11 backend.", + "https://crbug.com/dawn/348653642", ToggleStage::Device}}, {Toggle::UseT2B2TForSRGBTextureCopy, {"use_t2b2t_for_srgb_texture_copy", "Use T2B and B2T copies to emulate a T2T copy between sRGB and non-sRGB textures."
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h index ae751c0..16b2590 100644 --- a/src/dawn/native/Toggles.h +++ b/src/dawn/native/Toggles.h
@@ -114,6 +114,8 @@ UseBlitForFloat16TextureCopy, UseBlitForFloat32TextureCopy, UseBlitForT2B, + UseBlitForB2T, + D3D11DisableCPUUploadBuffers, UseT2B2TForSRGBTextureCopy, D3D12ReplaceAddWithMinusWhenDstFactorIsZeroAndSrcFactorIsDstAlpha, D3D12PolyfillReflectVec2F32,
diff --git a/src/dawn/native/d3d11/BufferD3D11.cpp b/src/dawn/native/d3d11/BufferD3D11.cpp index 804ac65..7af87ca 100644 --- a/src/dawn/native/d3d11/BufferD3D11.cpp +++ b/src/dawn/native/d3d11/BufferD3D11.cpp
@@ -41,6 +41,7 @@ #include "dawn/native/DynamicUploader.h" #include "dawn/native/d3d/D3DError.h" #include "dawn/native/d3d11/DeviceD3D11.h" +#include "dawn/native/d3d11/PhysicalDeviceD3D11.h" #include "dawn/native/d3d11/QueueD3D11.h" #include "dawn/native/d3d11/UtilsD3D11.h" #include "dawn/platform/DawnPlatform.h" @@ -52,6 +53,9 @@ namespace { +// Max size for a CPU buffer. +constexpr uint64_t kMaxCPUUploadBufferSize = 64 * 1024; + constexpr wgpu::BufferUsage kCopyUsages = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | kInternalCopySrcBuffer; @@ -136,16 +140,23 @@ return sizeof(float) * 4 * 16; } - if (usage & - (wgpu::BufferUsage::Storage | kInternalStorageBuffer | wgpu::BufferUsage::CopyDst)) { + if (usage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer | kReadOnlyStorageBuffer | + wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc)) { // Unordered access buffers must be 4-byte aligned. // We also align 4 bytes for CopyDst buffer since it would be used in T2B compute shader. // And that shader needs to write 4-byte chunks. + // Similarly, we need to align 4 bytes for CopySrc buffer since it would be used in B2T + // shader that reads 4 byte chunks. return sizeof(uint32_t); } return 1; } +bool CanUseCPUUploadBuffer(const Device* device, wgpu::BufferUsage usage, size_t bufferSize) { + return IsUpload(usage) && bufferSize <= kMaxCPUUploadBufferSize && + !device->IsToggleEnabled(Toggle::D3D11DisableCPUUploadBuffers); +} + constexpr size_t kConstantBufferUpdateAlignment = 16; } // namespace @@ -347,22 +358,55 @@ ComPtr<ID3D11Buffer> mD3d11Buffer; }; +bool CanAddStorageUsageToBufferWithoutSideEffects(const Device* device, + wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) { + // Don't support uniform buffers being used as storage buffer. Because D3D11 constant buffers + // cannot be bound to SRV or UAV. Allowing them to be used as storage buffer would require some + // workarounds including extra copies so it's better we prefer to not do that. + if (originalUsage & wgpu::BufferUsage::Uniform) { + return false; + } + + // If buffer is small, we prefer CPU buffer for uploading so don't allow adding storage usage. + if (CanUseCPUUploadBuffer(device, originalUsage, bufferSize)) { + return false; + } + + const bool requiresUAV = storageUsage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer); + // Check supports for writeable storage usage: + if (requiresUAV) { + // D3D11 mappable buffers cannot be used as UAV natively. So avoid that. + return !(originalUsage & kMappableBufferUsages); + } + + return true; +} + // static ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor, const ScopedCommandRecordingContext* commandContext, bool allowUploadBufferEmulation) { + const auto actualUsage = + ComputeInternalBufferUsages(device, descriptor->usage, descriptor->size); bool useUploadBuffer = allowUploadBufferEmulation; - useUploadBuffer &= IsUpload(descriptor->usage); - constexpr uint64_t kMaxUploadBufferSize = 4 * 1024 * 1024; - useUploadBuffer &= descriptor->size <= kMaxUploadBufferSize; + useUploadBuffer &= CanUseCPUUploadBuffer(device, actualUsage, descriptor->size); Ref<Buffer> buffer; if (useUploadBuffer) { buffer = AcquireRef(new UploadBuffer(device, descriptor)); - } else if (IsStaging(descriptor->usage)) { + } else if (IsStaging(actualUsage)) { buffer = AcquireRef(new StagingBuffer(device, descriptor)); } else { - buffer = AcquireRef(new GPUUsableBuffer(device, descriptor)); + const auto& devInfo = ToBackend(device->GetPhysicalDevice())->GetDeviceInfo(); + // Use D3D11_MAP_WRITE_NO_OVERWRITE when possible to guarantee driver that we don't + // overwrite data in use by GPU. MapAsync() already ensures that any GPU commands using this + // buffer already finish. In return driver won't try to stall CPU for mapping access. + D3D11_MAP mapWriteMode = devInfo.supportsMapNoOverwriteDynamicBuffers + ? D3D11_MAP_WRITE_NO_OVERWRITE + : D3D11_MAP_WRITE; + buffer = AcquireRef(new GPUUsableBuffer(device, descriptor, mapWriteMode)); } DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation, commandContext)); return buffer; @@ -775,10 +819,12 @@ // GPUUsableBuffer GPUUsableBuffer::GPUUsableBuffer(DeviceBase* device, - const UnpackedPtr<BufferDescriptor>& descriptor) + const UnpackedPtr<BufferDescriptor>& descriptor, + D3D11_MAP mapWriteMode) : Buffer(device, descriptor, - /*internalMappableFlags=*/descriptor->usage & kMappableBufferUsages) {} + /*internalMappableFlags=*/descriptor->usage & kMappableBufferUsages), + mD3DMapWriteMode(mapWriteMode) {} GPUUsableBuffer::~GPUUsableBuffer() = default; @@ -1071,10 +1117,7 @@ Storage* storage; if (mode == wgpu::MapMode::Write) { DAWN_ASSERT(!mCPUWritableStorage->IsStaging()); - // Use D3D11_MAP_WRITE_NO_OVERWRITE to guarantee driver that we don't overwrite data in - // use by GPU. MapAsync() already ensures that any GPU commands using this buffer - // already finish. In return driver won't try to stall CPU for mapping access. - mapType = D3D11_MAP_WRITE_NO_OVERWRITE; + mapType = mD3DMapWriteMode; storage = mCPUWritableStorage; } else { // Always map buffer with D3D11_MAP_READ_WRITE if possible even for mapping @@ -1193,9 +1236,10 @@ ResultOrError<ComPtr<ID3D11ShaderResourceView>> GPUUsableBuffer::CreateD3D11ShaderResourceViewFromD3DBuffer(ID3D11Buffer* d3d11Buffer, uint64_t offset, - uint64_t size) { + uint64_t originalSize) { + uint64_t size = Align(originalSize, 4); DAWN_ASSERT(IsAligned(offset, 4u)); - DAWN_ASSERT(IsAligned(size, 4u)); + DAWN_ASSERT(size <= GetAllocatedSize()); UINT firstElement = static_cast<UINT>(offset / 4); UINT numElements = static_cast<UINT>(size / 4); @@ -1217,9 +1261,10 @@ ResultOrError<ComPtr<ID3D11UnorderedAccessView1>> GPUUsableBuffer::CreateD3D11UnorderedAccessViewFromD3DBuffer(ID3D11Buffer* d3d11Buffer, uint64_t offset, - uint64_t size) { + uint64_t originalSize) { + uint64_t size = Align(originalSize, 4); DAWN_ASSERT(IsAligned(offset, 4u)); - DAWN_ASSERT(IsAligned(size, 4u)); + DAWN_ASSERT(size <= GetAllocatedSize()); UINT firstElement = static_cast<UINT>(offset / 4); UINT numElements = static_cast<UINT>(size / 4);
diff --git a/src/dawn/native/d3d11/BufferD3D11.h b/src/dawn/native/d3d11/BufferD3D11.h index d44782c..bf09d4d 100644 --- a/src/dawn/native/d3d11/BufferD3D11.h +++ b/src/dawn/native/d3d11/BufferD3D11.h
@@ -46,6 +46,11 @@ class ScopedCommandRecordingContext; class ScopedSwapStateCommandRecordingContext; +bool CanAddStorageUsageToBufferWithoutSideEffects(const Device* device, + wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize); + class Buffer : public BufferBase { public: static ResultOrError<Ref<Buffer>> Create(Device* device, @@ -195,7 +200,9 @@ // TODO(349848481): Consider making this the only Buffer class since it could cover all use cases. class GPUUsableBuffer final : public Buffer { public: - GPUUsableBuffer(DeviceBase* device, const UnpackedPtr<BufferDescriptor>& descriptor); + GPUUsableBuffer(DeviceBase* device, + const UnpackedPtr<BufferDescriptor>& descriptor, + D3D11_MAP mapWriteMode); ~GPUUsableBuffer() override; ResultOrError<ID3D11Buffer*> GetD3D11ConstantBuffer( @@ -331,6 +338,8 @@ using BufferViewKey = std::tuple<ID3D11Buffer*, uint64_t, uint64_t>; absl::flat_hash_map<BufferViewKey, ComPtr<ID3D11ShaderResourceView>> mSRVCache; absl::flat_hash_map<BufferViewKey, ComPtr<ID3D11UnorderedAccessView1>> mUAVCache; + + const D3D11_MAP mD3DMapWriteMode = D3D11_MAP_WRITE; }; static inline GPUUsableBuffer* ToGPUUsableBuffer(BufferBase* buffer) {
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp index ae05559..12a15c8 100644 --- a/src/dawn/native/d3d11/DeviceD3D11.cpp +++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -462,11 +462,11 @@ return true; } -bool Device::PreferNotUsingMappableOrUniformBufferAsStorage() const { - // D3D11 constant buffer or mappable buffer cannot be used as UAV. Allowing them to be used as - // storage buffer would require some workarounds including extra copies so it's better we - // prefer to not do that. - return true; +bool Device::CanAddStorageUsageToBufferWithoutSideEffects(wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) const { + return d3d11::CanAddStorageUsageToBufferWithoutSideEffects(this, storageUsage, originalUsage, + bufferSize); } uint32_t Device::GetUAVSlotCount() const {
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h index dcb16d0..935f4ac 100644 --- a/src/dawn/native/d3d11/DeviceD3D11.h +++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -77,7 +77,9 @@ bool MayRequireDuplicationOfIndirectParameters() const override; uint64_t GetBufferCopyOffsetAlignmentForDepthStencil() const override; bool CanTextureLoadResolveTargetInTheSameRenderpass() const override; - bool PreferNotUsingMappableOrUniformBufferAsStorage() const override; + bool CanAddStorageUsageToBufferWithoutSideEffects(wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) const override; void SetLabelImpl() override; void DisposeKeyedMutex(ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex) override;
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp index f6e45e2..29f3b73 100644 --- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp +++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -318,6 +318,7 @@ deviceToggles->ForceSet(Toggle::D3D11DisableFence, !mDeviceInfo.supportsNonMonitoredFence); } deviceToggles->Default(Toggle::UseBlitForT2B, true); + deviceToggles->Default(Toggle::UseBlitForB2T, true); auto deviceId = GetDeviceId(); auto vendorId = GetVendorId();
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp index 576f657..d601510 100644 --- a/src/dawn/native/vulkan/DeviceVk.cpp +++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -30,6 +30,7 @@ #include <algorithm> #include "dawn/common/Log.h" +#include "dawn/common/Math.h" #include "dawn/common/NonCopyable.h" #include "dawn/common/Platform.h" #include "dawn/common/Version_autogen.h" @@ -1075,9 +1076,15 @@ } } -bool Device::PreferNotUsingMappableOrUniformBufferAsStorage() const { - // Return true when the backend doesn't support mappable storage buffer - return !mSupportsMappableStorageBuffer; +bool Device::CanAddStorageUsageToBufferWithoutSideEffects(wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) const { + DAWN_ASSERT(IsSubset(storageUsage, wgpu::BufferUsage::Storage | kInternalStorageBuffer | + kReadOnlyStorageBuffer)); + if (originalUsage & kMappableBufferUsages) { + return mSupportsMappableStorageBuffer; + } + return true; } } // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h index d5ed961..056ebe2 100644 --- a/src/dawn/native/vulkan/DeviceVk.h +++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -126,7 +126,9 @@ // Used to associate this device with validation layer messages. const char* GetDebugPrefix() { return mDebugPrefix.c_str(); } - bool PreferNotUsingMappableOrUniformBufferAsStorage() const override; + bool CanAddStorageUsageToBufferWithoutSideEffects(wgpu::BufferUsage storageUsage, + wgpu::BufferUsage originalUsage, + size_t bufferSize) const override; private: Device(AdapterBase* adapter,
diff --git a/src/dawn/tests/end2end/CopyTests.cpp b/src/dawn/tests/end2end/CopyTests.cpp index 87823d1..f0b4a59 100644 --- a/src/dawn/tests/end2end/CopyTests.cpp +++ b/src/dawn/tests/end2end/CopyTests.cpp
@@ -27,7 +27,10 @@ #include <algorithm> #include <array> +#include <ostream> #include <sstream> +#include <string> +#include <type_traits> #include <vector> #include "dawn/common/Constants.h" @@ -50,6 +53,119 @@ format == wgpu::TextureFormat::R8Snorm; } +template <typename T, size_t NumComponents> +struct Color { + public: + static constexpr size_t kNumComponents = NumComponents; + static constexpr size_t kDataSize = sizeof(T) * NumComponents; + + Color() = default; + explicit Color(T value) { std::fill(components, components + NumComponents, value); } + + bool Equals(const Color& other, const Color& tolerance) const { + for (size_t i = 0; i < NumComponents; ++i) { + if (Diff(components[i], other.components[i]) > tolerance.components[i]) { + return false; + } + } + return true; + } + + std::string ToString() const { + std::ostringstream ss; + for (size_t i = 0; i < NumComponents; ++i) { + Print(ss, components[i]); + + if (i < NumComponents - 1) { + ss << " "; + } + } + + return ss.str(); + } + + protected: + static T Diff(T lhs, T rhs) { + if constexpr (std::is_integral_v<T>) { + return std::abs(static_cast<int64_t>(lhs) - static_cast<int64_t>(rhs)); + } + return std::abs(lhs - rhs); + } + + static std::ostream& Print(std::ostream& stream, T component) { + if constexpr (std::is_same_v<T, uint8_t>) { + return stream << static_cast<int>(component); + } + return stream << component; + } + + T components[NumComponents] = {}; +}; + +template <size_t NumComponents> +struct ColorF16 : public Color<uint16_t, NumComponents> { + public: + using Base = Color<uint16_t, NumComponents>; + using Base::Base; + + explicit ColorF16(float value) : Base(Float32ToFloat16(value)) {} + + bool Equals(const ColorF16& other, const ColorF16& tolerance) const { + for (size_t i = 0; i < NumComponents; ++i) { + if (abs(Float16ToFloat32(this->components[i]) - Float16ToFloat32(other.components[i])) > + Float16ToFloat32(tolerance.components[i])) { + return false; + } + } + return true; + } + + std::string ToString() const { + std::ostringstream ss; + for (size_t i = 0; i < NumComponents; ++i) { + ss << Float16ToFloat32(this->components[i]); + if (i < NumComponents - 1) { + ss << " "; + } + } + + return ss.str(); + } +}; + +static_assert(sizeof(Color<uint8_t, 1>) == 1, "Unexpected padding"); +static_assert(sizeof(Color<uint8_t, 2>) == 2, "Unexpected padding"); +static_assert(sizeof(Color<uint16_t, 1>) == 2, "Unexpected padding"); + +template <typename ColorType> +class ColorExpectation : public detail::CustomTextureExpectation { + public: + ColorExpectation(const ColorType* expected, size_t count, ColorType tolerance) + : mTolerance(tolerance) { + mExpected.assign(expected, expected + count); + } + + uint32_t DataSize() override { return ColorType::kDataSize; } + + testing::AssertionResult Check(const void* data, size_t size) override { + DAWN_ASSERT(size == sizeof(ColorType) * mExpected.size()); + const ColorType* actual = static_cast<const ColorType*>(data); + + for (size_t i = 0; i < mExpected.size(); ++i) { + if (!mExpected[i].Equals(actual[i], mTolerance)) { + return testing::AssertionFailure() + << "Expected data[" << i << "] to be " << mExpected[i].ToString() + << ", actual " << actual[i].ToString() << "\n"; + } + } + return testing::AssertionSuccess(); + } + + private: + std::vector<ColorType> mExpected; + ColorType mTolerance; +}; + class CopyTests { protected: struct TextureSpec { @@ -259,9 +375,9 @@ namespace { using TextureFormat = wgpu::TextureFormat; DAWN_TEST_PARAM_STRUCT(CopyTextureFormatParams, TextureFormat); -} // namespace -class CopyTests_T2B : public CopyTests, public DawnTestWithParams<CopyTextureFormatParams> { +class CopyTests_WithFormatParam : public CopyTests, + public DawnTestWithParams<CopyTextureFormatParams> { protected: struct TextureSpec : CopyTests::TextureSpec { TextureSpec() { format = GetParam().mTextureFormat; } @@ -269,17 +385,50 @@ std::vector<wgpu::FeatureName> GetRequiredFeatures() override { std::vector<wgpu::FeatureName> requiredFeatures = {}; - if (SupportsFeatures({wgpu::FeatureName::FlexibleTextureViews})) { - requiredFeatures.push_back(wgpu::FeatureName::FlexibleTextureViews); - } if (SupportsFeatures({wgpu::FeatureName::DawnTexelCopyBufferRowAlignment})) { requiredFeatures.push_back(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment); } return requiredFeatures; } + uint32_t GetTextureBytesPerRowAlignment() const { + if (!device.HasFeature(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) { + return kTextureBytesPerRowAlignment; + } + wgpu::Limits limits{}; + wgpu::DawnTexelCopyBufferRowAlignmentLimits alignmentLimits{}; + limits.nextInChain = &alignmentLimits; + device.GetLimits(&limits); + return alignmentLimits.minTexelCopyBufferRowAlignment; + } + BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height, uint32_t depth = 1) { + return CopyTests::MinimumBufferSpec(width, height, depth, GetParam().mTextureFormat, + GetTextureBytesPerRowAlignment()); + } + BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent, + uint32_t overrideBytesPerRow = kStrideComputeDefault, + uint32_t overrideRowsPerImage = kStrideComputeDefault) { + return CopyTests::MinimumBufferSpec(copyExtent, overrideBytesPerRow, overrideRowsPerImage, + GetParam().mTextureFormat, + GetTextureBytesPerRowAlignment()); + } +}; + +} // namespace + +class CopyTests_T2B : public CopyTests_WithFormatParam { + protected: + std::vector<wgpu::FeatureName> GetRequiredFeatures() override { + std::vector<wgpu::FeatureName> requiredFeatures = + CopyTests_WithFormatParam::GetRequiredFeatures(); + if (SupportsFeatures({wgpu::FeatureName::FlexibleTextureViews})) { + requiredFeatures.push_back(wgpu::FeatureName::FlexibleTextureViews); + } + return requiredFeatures; + } + void SetUp() override { - DawnTestWithParams<CopyTextureFormatParams>::SetUp(); + CopyTests_WithFormatParam::SetUp(); auto format = GetParam().mTextureFormat; @@ -306,27 +455,6 @@ format == wgpu::TextureFormat::RG11B10Ufloat) && (IsD3D11() || IsOpenGLES()) && IsIntelGen12()); } - uint32_t GetTextureBytesPerRowAlignment() const { - if (!device.HasFeature(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) { - return kTextureBytesPerRowAlignment; - } - wgpu::Limits limits{}; - wgpu::DawnTexelCopyBufferRowAlignmentLimits alignmentLimits{}; - limits.nextInChain = &alignmentLimits; - device.GetLimits(&limits); - return alignmentLimits.minTexelCopyBufferRowAlignment; - } - BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height, uint32_t depth = 1) { - return CopyTests::MinimumBufferSpec(width, height, depth, GetParam().mTextureFormat, - GetTextureBytesPerRowAlignment()); - } - BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent, - uint32_t overrideBytesPerRow = kStrideComputeDefault, - uint32_t overrideRowsPerImage = kStrideComputeDefault) { - return CopyTests::MinimumBufferSpec(copyExtent, overrideBytesPerRow, overrideRowsPerImage, - GetParam().mTextureFormat, - GetTextureBytesPerRowAlignment()); - } void DoTest( const TextureSpec& textureSpec, @@ -480,29 +608,86 @@ } }; -class CopyTests_B2T : public CopyTests, public DawnTest { +class CopyTests_B2T : public CopyTests_WithFormatParam { protected: - static void FillBufferData(utils::RGBA8* data, size_t count) { - for (size_t i = 0; i < count; ++i) { - data[i] = - utils::RGBA8(static_cast<uint8_t>(i % 256), static_cast<uint8_t>((i / 256) % 256), - static_cast<uint8_t>((i / 256 / 256) % 256), 255); - } - } - void DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec, const wgpu::Extent3D& copySize, wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) { - // TODO(crbug.com/dawn/818): support testing arbitrary formats - ASSERT_EQ(kDefaultFormat, textureSpec.format); - // Create a buffer of size `size` and populate it with data - const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format); - std::vector<utils::RGBA8> bufferData(bufferSpec.size / bytesPerTexel); - FillBufferData(bufferData.data(), bufferData.size()); - wgpu::Buffer buffer = - utils::CreateBufferFromData(device, bufferData.data(), bufferSpec.size, - wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); + switch (textureSpec.format) { + case wgpu::TextureFormat::R8Unorm: + DoTestImpl<Color<uint8_t, 1>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/Color<uint8_t, 1>(1)); + break; + case wgpu::TextureFormat::RG8Unorm: + DoTestImpl<Color<uint8_t, 2>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/Color<uint8_t, 2>(1)); + break; + case wgpu::TextureFormat::RGBA8Unorm: + case wgpu::TextureFormat::BGRA8Unorm: + DoTestImpl<Color<uint8_t, 4>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/Color<uint8_t, 4>(1)); + break; + case wgpu::TextureFormat::R16Float: + DoTestImpl<ColorF16<1>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/ColorF16<1>(0.001f)); + break; + case wgpu::TextureFormat::RG16Float: + DoTestImpl<ColorF16<2>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/ColorF16<2>(0.001f)); + break; + case wgpu::TextureFormat::RGBA16Float: + DoTestImpl<ColorF16<4>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/ColorF16<4>(0.001f)); + break; + case wgpu::TextureFormat::R32Float: + DoTestImpl<Color<float, 1>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/Color<float, 1>(0.0001f)); + break; + case wgpu::TextureFormat::RG32Float: + DoTestImpl<Color<float, 2>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/Color<float, 2>(0.0001f)); + break; + case wgpu::TextureFormat::RGBA32Float: + DoTestImpl<Color<float, 4>>(textureSpec, bufferSpec, copySize, dimension, + /*tolerance=*/Color<float, 4>(0.0001f)); + break; + default: + DAWN_UNREACHABLE(); + } + } + + template <class PixelType> + void DoTestImpl(const TextureSpec& textureSpec, + const BufferSpec& bufferSpec, + const wgpu::Extent3D& copySize, + wgpu::TextureDimension dimension, + PixelType tolerance = {}) { + const uint32_t bytesPerTexel = PixelType::kDataSize; + DAWN_ASSERT(bytesPerTexel == utils::GetTexelBlockSizeInBytes(textureSpec.format)); + const utils::TextureDataCopyLayout copyLayout = + utils::GetTextureDataCopyLayoutForTextureAtLevel( + textureSpec.format, textureSpec.textureSize, textureSpec.copyLevel, dimension, + bufferSpec.rowsPerImage, GetTextureBytesPerRowAlignment()); + + // Create a buffer and populate it with data + wgpu::Buffer buffer; + std::vector<uint8_t> bufferData(bufferSpec.offset, 0xff); + { + const std::vector<uint8_t> copyData = + GetExpectedTextureData(textureSpec.format, copyLayout); + + bufferData.insert(bufferData.end(), copyData.begin(), copyData.end()); + bufferData.resize(Align(bufferSpec.size, 4)); + + wgpu::BufferDescriptor descriptor; + descriptor.size = bufferData.size(); + descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite; + descriptor.mappedAtCreation = true; + buffer = device.CreateBuffer(&descriptor); + memcpy(buffer.GetMappedRange(), bufferData.data(), bufferData.size()); + buffer.Unmap(); + } // Create a texture that is `width` x `height` with (`level` + 1) mip levels. wgpu::TextureDescriptor descriptor; @@ -525,11 +710,6 @@ wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); - const utils::TextureDataCopyLayout copyLayout = - utils::GetTextureDataCopyLayoutForTextureAtLevel( - textureSpec.format, textureSpec.textureSize, textureSpec.copyLevel, dimension, - bufferSpec.rowsPerImage); - uint32_t copyLayer = copySize.depthOrArrayLayers; uint32_t copyDepth = 1; if (dimension == wgpu::TextureDimension::e3D) { @@ -546,16 +726,19 @@ for (uint32_t layer = 0; layer < copyLayer; ++layer) { // Copy and pack the data used to create the buffer in the specified copy region to have // the same format as the expected texture data. - std::vector<utils::RGBA8> expected(texelCountPerLayer); - CopyTextureData(bytesPerTexel, bufferData.data() + bufferOffset / bytesPerTexel, - copySize.width, copySize.height, copyDepth, bufferSpec.bytesPerRow, + std::vector<PixelType> expected(texelCountPerLayer); + CopyTextureData(bytesPerTexel, bufferData.data() + bufferOffset, copySize.width, + copySize.height, copyDepth, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage, expected.data(), copySize.width * bytesPerTexel, copySize.height); - EXPECT_TEXTURE_EQ(expected.data(), texture, - {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, - textureSpec.copyOrigin.z + layer}, - {copySize.width, copySize.height, copyDepth}, textureSpec.copyLevel) + EXPECT_TEXTURE_EQ( + new ColorExpectation<PixelType>( + expected.data(), copySize.width * copySize.height * copyDepth, tolerance), + texture, + {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, + textureSpec.copyOrigin.z + layer}, + {copySize.width, copySize.height, copyDepth}, textureSpec.copyLevel) << "Buffer to Texture copy failed copying " << bufferSpec.size << "-byte buffer with offset " << bufferSpec.offset << " and bytes per row " << bufferSpec.bytesPerRow << " to [(" << textureSpec.copyOrigin.x << ", " @@ -2183,6 +2366,8 @@ BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); bufferSpec.size += i; bufferSpec.offset += i; + bufferSpec.size = Align(bufferSpec.size, bytesPerTexel); + bufferSpec.offset = Align(bufferSpec.offset, bytesPerTexel); DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1}); } } @@ -2414,16 +2599,17 @@ TextureSpec textureSpec; textureSpec.textureSize = {kWidth, kHeight, kDepth}; + const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format); BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth); // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12. // Base: no split for a row + no empty first row - bufferSpec.offset = 60; + bufferSpec.offset = Align(60u, bytesPerTexel); bufferSpec.size += bufferSpec.offset; DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D); // This test will cover: no split for a row + empty first row - bufferSpec.offset = 260; + bufferSpec.offset = Align(260u, bytesPerTexel); bufferSpec.size += bufferSpec.offset; DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D); } @@ -2435,11 +2621,12 @@ TextureSpec textureSpec; textureSpec.textureSize = {kWidth, kHeight, kDepth}; + const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format); BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth); // The test below is designed to test TextureCopySplitter for 3D textures on D3D12. // This test will cover: split for a row + no empty first row for both split regions - bufferSpec.offset = 260; + bufferSpec.offset = Align(260u, bytesPerTexel); bufferSpec.size += bufferSpec.offset; DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D); } @@ -2475,16 +2662,17 @@ TextureSpec textureSpec; textureSpec.textureSize = {kWidth, kHeight, kDepth}; + const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format); BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth); // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12. // Base: no split for a row, no empty row, and copy height is 1 - bufferSpec.offset = 60; + bufferSpec.offset = Align(60u, bytesPerTexel); bufferSpec.size += bufferSpec.offset; DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D); // This test will cover: no split for a row + empty first row, and copy height is 1 - bufferSpec.offset = 260; + bufferSpec.offset = Align(260u, bytesPerTexel); bufferSpec.size += bufferSpec.offset; DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D); } @@ -2550,13 +2738,42 @@ } } -DAWN_INSTANTIATE_TEST(CopyTests_B2T, - D3D11Backend(), - D3D12Backend(), - MetalBackend(), - OpenGLBackend(), - OpenGLESBackend(), - VulkanBackend()); +// Test that copying a texture 1D works. +TEST_P(CopyTests_B2T, Texture1DFull) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 1; + constexpr uint32_t kDepth = 1; + + TextureSpec textureSpec; + textureSpec.textureSize = {kWidth, kHeight, kDepth}; + + DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kDepth), {kWidth, kHeight, kDepth}, + wgpu::TextureDimension::e1D); +} + +DAWN_INSTANTIATE_TEST_P(CopyTests_B2T, + {D3D11Backend(), D3D11Backend({"d3d11_disable_cpu_buffers"}), + D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), + VulkanBackend()}, + { + wgpu::TextureFormat::R8Unorm, + wgpu::TextureFormat::RG8Unorm, + wgpu::TextureFormat::RGBA8Unorm, + + wgpu::TextureFormat::R16Float, + + wgpu::TextureFormat::RG16Float, + + wgpu::TextureFormat::R32Float, + + wgpu::TextureFormat::RG32Float, + + wgpu::TextureFormat::RGBA16Float, + + wgpu::TextureFormat::RGBA32Float, + + wgpu::TextureFormat::BGRA8Unorm, + }); TEST_P(CopyTests_T2T, Texture) { constexpr uint32_t kWidth = 256;
diff --git a/src/dawn/tests/end2end/TextureZeroInitTests.cpp b/src/dawn/tests/end2end/TextureZeroInitTests.cpp index 015016a..f865bf4 100644 --- a/src/dawn/tests/end2end/TextureZeroInitTests.cpp +++ b/src/dawn/tests/end2end/TextureZeroInitTests.cpp
@@ -351,6 +351,11 @@ // Test for a copy only to a subset of the subresource, lazy init is necessary to clear the other // half. TEST_P(TextureZeroInitTest, CopyBufferToTextureHalf) { + // TODO(348653642): D3D11 emulates B2T with a render pass, and render pass' lazy clear + // is not currently counted properly. So GetLazyClearCountForTesting() would not return the + // expected value. + DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("use_blit_for_b2t")); + wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(4, 1, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding |
diff --git a/src/dawn/utils/TestUtils.cpp b/src/dawn/utils/TestUtils.cpp index 62c59c4..b0cb4bb 100644 --- a/src/dawn/utils/TestUtils.cpp +++ b/src/dawn/utils/TestUtils.cpp
@@ -61,11 +61,13 @@ return Align(bytesPerBlock * (width / blockWidth), textureBytesPerRowAlignment); } -TextureDataCopyLayout GetTextureDataCopyLayoutForTextureAtLevel(wgpu::TextureFormat format, - wgpu::Extent3D textureSizeAtLevel0, - uint32_t mipmapLevel, - wgpu::TextureDimension dimension, - uint32_t rowsPerImage) { +TextureDataCopyLayout GetTextureDataCopyLayoutForTextureAtLevel( + wgpu::TextureFormat format, + wgpu::Extent3D textureSizeAtLevel0, + uint32_t mipmapLevel, + wgpu::TextureDimension dimension, + uint32_t rowsPerImage, + uint32_t textureBytesPerRowAlignment) { // Compressed texture formats not supported in this function yet. DAWN_ASSERT(dawn::utils::GetTextureFormatBlockWidth(format) == 1); @@ -80,7 +82,8 @@ std::max(textureSizeAtLevel0.depthOrArrayLayers >> mipmapLevel, 1u); } - layout.bytesPerRow = GetMinimumBytesPerRow(format, layout.mipSize.width); + layout.bytesPerRow = + GetMinimumBytesPerRow(format, layout.mipSize.width, textureBytesPerRowAlignment); if (rowsPerImage == wgpu::kCopyStrideUndefined) { rowsPerImage = layout.mipSize.height;
diff --git a/src/dawn/utils/TestUtils.h b/src/dawn/utils/TestUtils.h index 0e4e8e9..c943cda 100644 --- a/src/dawn/utils/TestUtils.h +++ b/src/dawn/utils/TestUtils.h
@@ -76,7 +76,8 @@ wgpu::Extent3D textureSizeAtLevel0, uint32_t mipmapLevel, wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D, - uint32_t rowsPerImage = wgpu::kCopyStrideUndefined); + uint32_t rowsPerImage = wgpu::kCopyStrideUndefined, + uint32_t textureBytesPerRowAlignment = kTextureBytesPerRowAlignment); uint64_t RequiredBytesInCopy(uint64_t bytesPerRow, uint64_t rowsPerImage,