| // 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 "src/dawn/native/webgpu/TextureWGPU.h" | |
| #include <algorithm> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/dawn/common/StringViewUtils.h" | |
| #include "src/dawn/native/BlockInfo.h" | |
| #include "src/dawn/native/EnumMaskIterator.h" | |
| #include "src/dawn/native/webgpu/CaptureContext.h" | |
| #include "src/dawn/native/webgpu/DeviceWGPU.h" | |
| #include "src/dawn/native/webgpu/QueueWGPU.h" | |
| #include "src/dawn/native/webgpu/SharedFenceWGPU.h" | |
| #include "src/dawn/native/webgpu/SharedTextureMemoryWGPU.h" | |
| #include "src/dawn/native/webgpu/ToWGPU.h" | |
| namespace dawn::native::webgpu { | |
| // static | |
| ResultOrError<Ref<Texture>> Texture::Create(Device* device, | |
| const UnpackedPtr<TextureDescriptor>& descriptor) { | |
| return AcquireRef(new Texture(device, descriptor)); | |
| } | |
| // static | |
| Ref<Texture> Texture::CreateFromSharedTextureMemory( | |
| const SharedTextureMemory* memory, | |
| const UnpackedPtr<TextureDescriptor>& descriptor) { | |
| Device* device = ToBackend(memory->GetDevice()); | |
| return AcquireRef(new Texture(device, descriptor, memory)); | |
| } | |
| // static | |
| Ref<Texture> Texture::CreateFromSurfaceTexture(Device* device, | |
| const UnpackedPtr<TextureDescriptor>& descriptor, | |
| const WGPUSurfaceTexture& surfaceTexture) { | |
| return AcquireRef(new Texture(device, descriptor, surfaceTexture)); | |
| } | |
| struct ComboTextureDescriptor { | |
| WGPUTextureDescriptor desc = {}; | |
| std::vector<WGPUTextureFormat> viewFormats; | |
| std::string label; | |
| explicit ComboTextureDescriptor(const Texture* texture) { | |
| Device* device = ToBackend(texture->GetDevice()); | |
| wgpu::TextureUsage actualUsage = texture->GetInternalUsage(); | |
| // Resolve internal usages to regular ones | |
| if (actualUsage & kReadOnlyStorageTexture) { | |
| actualUsage &= ~kReadOnlyStorageTexture; | |
| } | |
| if (actualUsage & kWriteOnlyStorageTexture) { | |
| actualUsage &= ~kWriteOnlyStorageTexture; | |
| } | |
| if (actualUsage & kReadOnlyRenderAttachment) { | |
| actualUsage &= ~kReadOnlyRenderAttachment; | |
| } | |
| if (actualUsage & kResolveAttachmentLoadingUsage) { | |
| actualUsage &= ~kResolveAttachmentLoadingUsage; | |
| } | |
| if (!(actualUsage & wgpu::TextureUsage::TransientAttachment)) { | |
| actualUsage |= wgpu::TextureUsage::CopySrc; | |
| } | |
| viewFormats.reserve(texture->GetViewFormats().size()); | |
| for (FormatIndex i : texture->GetViewFormats()) { | |
| viewFormats.push_back(ToAPI(device->GetValidInternalFormat(i).format)); | |
| } | |
| label = texture->GetLabel(); | |
| desc = { | |
| .nextInChain = nullptr, | |
| .label = ToOutputStringView(label), | |
| .usage = ToAPI(actualUsage), | |
| .dimension = ToAPI(texture->GetDimension()), | |
| .size = ToWGPU(texture->GetBaseSize()), | |
| .format = ToAPI(texture->GetFormat().format), | |
| .mipLevelCount = texture->GetNumMipLevels(), | |
| .sampleCount = texture->GetSampleCount(), | |
| .viewFormatCount = static_cast<uint32_t>(viewFormats.size()), | |
| .viewFormats = viewFormats.data(), | |
| }; | |
| } | |
| }; | |
| Texture::Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor) | |
| : TextureBase(device, descriptor), | |
| RecordableObject(schema::ObjectType::Texture), | |
| ObjectWGPU(device->wgpu->textureRelease) { | |
| ComboTextureDescriptor comboDesc(this); | |
| mInnerHandle = device->wgpu->deviceCreateTexture(device->GetInnerHandle(), &comboDesc.desc); | |
| DAWN_ASSERT(mInnerHandle); | |
| } | |
| Texture::Texture(Device* device, | |
| const UnpackedPtr<TextureDescriptor>& descriptor, | |
| const SharedTextureMemory* memory) | |
| : TextureBase(device, descriptor), | |
| RecordableObject(schema::ObjectType::Texture), | |
| ObjectWGPU(device->wgpu->textureRelease) { | |
| ComboTextureDescriptor comboDesc(this); | |
| mInnerHandle = | |
| device->wgpu->sharedTextureMemoryCreateTexture(memory->GetInnerHandle(), &comboDesc.desc); | |
| mSharedResourceMemoryContents = memory->GetContents(); | |
| // TODO(crbug.com/500368961): Generalize wgpu::SharedTextureMemoryD3DSwapchainBeginState | |
| // to all platform and use that to indicate if it's a SwapChain texture (assigning | |
| // mIsSurfaceTexture) and mark frame boundary. | |
| DAWN_ASSERT(mInnerHandle); | |
| } | |
| Texture::Texture(Device* device, | |
| const UnpackedPtr<TextureDescriptor>& descriptor, | |
| const WGPUSurfaceTexture& surfaceTexture) | |
| : TextureBase(device, descriptor), | |
| RecordableObject(schema::ObjectType::Texture), | |
| ObjectWGPU(device->wgpu->textureRelease) { | |
| mInnerHandle = surfaceTexture.texture; | |
| mIsSurfaceTexture = true; | |
| DAWN_ASSERT(mInnerHandle); | |
| } | |
| void Texture::DestroyImpl(DestroyReason reason) { | |
| TextureBase::DestroyImpl(reason); | |
| auto& wgpu = ToBackend(GetDevice())->wgpu.get(); | |
| wgpu.textureDestroy(mInnerHandle); | |
| } | |
| void Texture::SetLabelImpl() { | |
| ToBackend(GetDevice())->CaptureSetLabel(this, GetLabel()); | |
| } | |
| ExecutionSerial Texture::OnEndAccess() { | |
| mPendingBeginAccess = false; | |
| return TextureBase::OnEndAccess(); | |
| } | |
| void Texture::SynchronizeTextureBeforeUse() { | |
| if (SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents()) { | |
| SharedTextureMemoryBase::PendingFenceList fences; | |
| contents->AcquirePendingFences(&fences); | |
| if (mPendingBeginAccess) { | |
| auto srm = contents->GetSharedResourceMemory().Promote(); | |
| DAWN_ASSERT(srm != nullptr); | |
| auto* stm = static_cast<SharedTextureMemory*>(srm.Get()); | |
| WGPUSharedTextureMemoryBeginAccessDescriptor innerDesc = | |
| WGPU_SHARED_TEXTURE_MEMORY_BEGIN_ACCESS_DESCRIPTOR_INIT; | |
| innerDesc.concurrentRead = mPendingConcurrentRead; | |
| innerDesc.initialized = mPendingInitialized; | |
| std::vector<WGPUSharedFence> innerFences; | |
| std::vector<uint64_t> signaledValues; | |
| innerFences.reserve(fences.size()); | |
| signaledValues.reserve(fences.size()); | |
| for (const auto& fence : fences) { | |
| innerFences.push_back(ToBackend(fence.object)->GetInnerHandle()); | |
| signaledValues.push_back(fence.signaledValue); | |
| } | |
| innerDesc.fenceCount = innerFences.size(); | |
| innerDesc.fences = innerFences.data(); | |
| innerDesc.signaledValueCount = signaledValues.size(); | |
| innerDesc.signaledValues = signaledValues.data(); | |
| const DawnProcTable& wgpu = ToBackend(GetDevice())->wgpu.get(); | |
| WGPUStatus status = wgpu.sharedTextureMemoryBeginAccess(stm->GetInnerHandle(), | |
| GetInnerHandle(), &innerDesc); | |
| DAWN_ASSERT(status == WGPUStatus_Success); | |
| mPendingBeginAccess = false; | |
| } | |
| } | |
| // Update the last serial to sync frontend state. | |
| mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial(); | |
| } | |
| void Texture::SetPendingBeginAccess(bool concurrentRead, bool initialized) { | |
| mPendingBeginAccess = true; | |
| mPendingConcurrentRead = concurrentRead; | |
| mPendingInitialized = initialized; | |
| } | |
| // TextureView | |
| // static | |
| ResultOrError<Ref<TextureView>> TextureView::Create( | |
| TextureBase* texture, | |
| const UnpackedPtr<TextureViewDescriptor>& descriptor) { | |
| Device* device = ToBackend(texture->GetDevice()); | |
| auto* desc = ToAPI(*descriptor); | |
| WGPUTextureView innerView = | |
| device->wgpu->textureCreateView(ToBackend(texture)->GetInnerHandle(), desc); | |
| DAWN_ASSERT(innerView); | |
| return AcquireRef(new TextureView(texture, descriptor, innerView)); | |
| } | |
| TextureView::TextureView(TextureBase* texture, | |
| const UnpackedPtr<TextureViewDescriptor>& descriptor, | |
| WGPUTextureView innerView) | |
| : TextureViewBase(texture, descriptor), | |
| RecordableObject(schema::ObjectType::TextureView), | |
| ObjectWGPU(ToBackend(texture->GetDevice())->wgpu->textureViewRelease) { | |
| mInnerHandle = innerView; | |
| } | |
| MaybeError Texture::AddReferenced(CaptureContext& captureContext) { | |
| // Textures do not reference other objects. | |
| return {}; | |
| } | |
| MaybeError Texture::CaptureCreationParameters(CaptureContext& captureContext) { | |
| Device* device = ToBackend(GetDevice()); | |
| std::vector<wgpu::TextureFormat> viewFormats; | |
| for (FormatIndex i : GetViewFormats()) { | |
| const Format& viewFormat = device->GetValidInternalFormat(i); | |
| viewFormats.emplace_back(viewFormat.format); | |
| } | |
| schema::Texture tex{{ | |
| .usage = GetUsage(), | |
| .dimension = GetDimension(), | |
| .size = ToSchema(GetBaseSize()), | |
| .format = GetFormat().format, | |
| .mipLevelCount = GetNumMipLevels(), | |
| .sampleCount = GetSampleCount(), | |
| .viewFormats = viewFormats, | |
| .isSurfaceTexture = mIsSurfaceTexture, | |
| }}; | |
| Serialize(captureContext, tex); | |
| return {}; | |
| } | |
| namespace { | |
| bool IsDepthAspectDepth24Plus(const Format& format, Aspect aspect) { | |
| return aspect == Aspect::Depth && (format.format == wgpu::TextureFormat::Depth24Plus || | |
| format.format == wgpu::TextureFormat::Depth24PlusStencil8); | |
| } | |
| MaybeError MapBufferAndWriteTextureData(CaptureContext::ScopedContentWriter& writer, | |
| Device* device, | |
| WGPUBuffer copyBuffer, | |
| BlockCount blockRows, | |
| uint32_t alignedBytesPerRow, | |
| uint32_t mappableBytesPerRow, | |
| uint32_t usedBytesPerRow) { | |
| struct MapAsyncResult { | |
| WGPUMapAsyncStatus status; | |
| std::string message; | |
| } mapAsyncResult = {}; | |
| auto& wgpu = device->wgpu; | |
| // Map the buffer to read back the content. | |
| WGPUBufferMapCallbackInfo innerCallbackInfo = {}; | |
| innerCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; | |
| innerCallbackInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView message, | |
| void* result_param, void* userdata_param) { | |
| MapAsyncResult* result = reinterpret_cast<MapAsyncResult*>(result_param); | |
| result->status = status; | |
| result->message = ToString(message); | |
| }; | |
| innerCallbackInfo.userdata1 = &mapAsyncResult; | |
| // Read this back synchronously. | |
| WGPUFutureWaitInfo waitInfo = {}; | |
| uint64_t offset = 0; | |
| waitInfo.future = wgpu->bufferMapAsync(copyBuffer, WGPUMapMode_Read, offset, | |
| CaptureContext::kCopyBufferSize, innerCallbackInfo); | |
| wgpu->instanceWaitAny(device->GetInnerInstance(), 1, &waitInfo, UINT64_MAX); | |
| DAWN_ASSERT(mapAsyncResult.status == WGPUMapAsyncStatus_Success); | |
| if (mapAsyncResult.status != WGPUMapAsyncStatus_Success) { | |
| return DAWN_INTERNAL_ERROR(mapAsyncResult.message); | |
| } | |
| // We only write out the beginning of each row, the rest is padding. | |
| for (BlockCount blockRow{0u}; blockRow < blockRows; ++blockRow) { | |
| const void* data = wgpu->bufferGetConstMappedRange( | |
| copyBuffer, dchecked_cast<uint32_t>(blockRow) * alignedBytesPerRow, | |
| mappableBytesPerRow); | |
| writer.WriteContentBytes(data, usedBytesPerRow); | |
| } | |
| wgpu->bufferUnmap(copyBuffer); | |
| return {}; | |
| } | |
| MaybeError CopyTextureRegionToBuffer(Device* device, | |
| const WGPUTexelCopyTextureInfo& srcTexture, | |
| const WGPUTexelCopyBufferInfo& dstBuffer, | |
| const WGPUExtent3D& copySize) { | |
| WGPUDevice innerDevice = device->GetInnerHandle(); | |
| WGPUQueue queue = ToBackend(device->GetQueue())->GetInnerHandle(); | |
| auto& wgpu = device->wgpu.get(); | |
| WGPUCommandEncoder encoder = wgpu.deviceCreateCommandEncoder(innerDevice, nullptr); | |
| wgpu.commandEncoderCopyTextureToBuffer(encoder, &srcTexture, &dstBuffer, ©Size); | |
| WGPUCommandBuffer commandBuffer = wgpu.commandEncoderFinish(encoder, nullptr); | |
| wgpu.queueSubmit(queue, 1, &commandBuffer); | |
| wgpu.commandBufferRelease(commandBuffer); | |
| wgpu.commandEncoderRelease(encoder); | |
| return {}; | |
| } | |
| } // namespace | |
| // TODO(451559917): Make this a helper for copying a texture to memory N bytes at a time. | |
| // so that other parts of dawn can use it. | |
| MaybeError Texture::CaptureContentIfNeeded(CaptureContext& captureContext, | |
| schema::ObjectId id, | |
| bool newResource) { | |
| // If it's all zeros or it's transient we don't need to capture it. | |
| if (!IsInitialized() || !newResource || | |
| (GetUsage() & wgpu::TextureUsage::TransientAttachment)) { | |
| return {}; | |
| } | |
| WGPUBuffer copyBuffer = captureContext.GetCopyBuffer(); | |
| Device* device = ToBackend(GetDevice()); | |
| // TODO(473870505): multi-planar textures. | |
| // Also, this can't handle compressed textures on compat as they are not readable (no copyT2B) | |
| auto format = GetFormat(); | |
| for (dawn::native::Aspect aspect : IterateEnumMask(format.aspects)) { | |
| const TypedTexelBlockInfo& blockInfo = format.GetAspectInfo(aspect).block; | |
| // Check if (aspect == depth && depth24plus) ASSERT(byteSize == 4) | |
| // This is because there really is no size for depth24plus but it's conveniently | |
| // set to 4 in Format.cpp. The code below relies on this as it's going to use | |
| // r32float as a substitute for depth24plus and r32float has a size of 4. | |
| DAWN_ASSERT(!IsDepthAspectDepth24Plus(format, aspect) || blockInfo.byteSize == 4); | |
| // For each mip level copy the texture to a buffer, map it, and write the buffer data for | |
| // that level. | |
| for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) { | |
| auto size = TexelExtent3D(GetMipLevelSubresourcePhysicalSize(mipLevel, aspect)); | |
| auto blockSize = blockInfo.ToBlock(size); | |
| uint32_t usedBytesPerRow = uint32_t(blockInfo.ToBytes(blockSize.width)); | |
| uint32_t mappableBytesPerRow = RoundUp(usedBytesPerRow, 4); | |
| schema::RootCommandInitTextureCmd cmd{{ | |
| .data = {{ | |
| .destination = {{ | |
| .textureId = id, | |
| .mipLevel = mipLevel, | |
| .origin = {{.x = 0, .y = 0, .z = 0}}, | |
| .aspect = ToDawn(aspect), | |
| }}, | |
| .layout = {{ | |
| .offset = 0, | |
| .bytesPerRow = usedBytesPerRow, | |
| .rowsPerImage = dchecked_cast<uint32_t>(blockSize.height), | |
| }}, | |
| .size = {{ | |
| .width = dchecked_cast<uint32_t>(size.width), | |
| .height = dchecked_cast<uint32_t>(size.height), | |
| .depthOrArrayLayers = dchecked_cast<uint32_t>(size.depthOrArrayLayers), | |
| }}, | |
| .dataSize = blockInfo.ToBytes(blockSize.width * blockSize.height * | |
| blockSize.depthOrArrayLayers), | |
| }}, | |
| }}; | |
| Serialize(captureContext, cmd); | |
| CaptureContext::ScopedContentWriter writer(captureContext); | |
| uint32_t alignedBytesPerRow = Align(usedBytesPerRow, 256); | |
| BlockCount maxBlockRowsPerRead{CaptureContext::kCopyBufferSize / alignedBytesPerRow}; | |
| DAWN_ASSERT(maxBlockRowsPerRead > BlockCount{0u}); | |
| for (BlockCount z{0u}; z < blockSize.depthOrArrayLayers; ++z) { | |
| for (BlockCount y{0u}; y < blockSize.height; y += maxBlockRowsPerRead) { | |
| BlockCount blockRows = std::min(maxBlockRowsPerRead, blockSize.height - y); | |
| // Copy Data from Texture to Buffer. Then map and write buffer. | |
| WGPUTexelCopyTextureInfo srcTexture{ | |
| .texture = GetInnerHandle(), | |
| .mipLevel = mipLevel, | |
| .origin = | |
| { | |
| .x = 0, | |
| .y = dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(y)), | |
| .z = dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(z)), | |
| }, | |
| .aspect = ToWGPU(aspect), | |
| }; | |
| WGPUTexelCopyBufferInfo dstBuffer{ | |
| .layout = | |
| { | |
| .offset = 0, | |
| .bytesPerRow = alignedBytesPerRow, | |
| .rowsPerImage = dchecked_cast<uint32_t>(blockRows), | |
| }, | |
| .buffer = copyBuffer, | |
| }; | |
| WGPUExtent3D copySize{ | |
| .width = dchecked_cast<uint32_t>(blockInfo.ToTexelWidth(blockSize.width)), | |
| .height = dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(blockRows)), | |
| .depthOrArrayLayers = 1, | |
| }; | |
| DAWN_TRY(CopyTextureRegionToBuffer(device, srcTexture, dstBuffer, copySize)); | |
| DAWN_TRY(MapBufferAndWriteTextureData(writer, device, copyBuffer, blockRows, | |
| alignedBytesPerRow, mappableBytesPerRow, | |
| usedBytesPerRow)); | |
| } | |
| } | |
| } | |
| } | |
| return {}; | |
| } | |
| void TextureView::SetLabelImpl() { | |
| ToBackend(GetDevice())->CaptureSetLabel(this, GetLabel()); | |
| } | |
| MaybeError TextureView::AddReferenced(CaptureContext& captureContext) { | |
| return captureContext.AddResource(ToBackend(GetTexture())); | |
| } | |
| MaybeError TextureView::CaptureCreationParameters(CaptureContext& captureContext) { | |
| schema::TextureView tex{{ | |
| .textureId = captureContext.GetId(GetTexture()), | |
| .format = GetFormat().format, | |
| .dimension = GetDimension(), | |
| .baseMipLevel = GetBaseMipLevel(), | |
| .mipLevelCount = GetLevelCount(), | |
| .baseArrayLayer = GetBaseArrayLayer(), | |
| .arrayLayerCount = GetLayerCount(), | |
| .aspect = ToDawn(GetAspects()), | |
| .usage = GetUsage(), | |
| .swizzle = {{ | |
| .r = GetSwizzle().r, | |
| .g = GetSwizzle().g, | |
| .b = GetSwizzle().b, | |
| .a = GetSwizzle().a, | |
| }}, | |
| }}; | |
| Serialize(captureContext, tex); | |
| return {}; | |
| } | |
| } // namespace dawn::native::webgpu |