blob: 0f0dd0ad42de08d52fd098406e84bea5c196a7e4 [file] [log] [blame]
// 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/webgpu/TextureWGPU.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "dawn/common/StringViewUtils.h"
#include "dawn/native/BlockInfo.h"
#include "dawn/native/webgpu/CaptureContext.h"
#include "dawn/native/webgpu/DeviceWGPU.h"
#include "dawn/native/webgpu/QueueWGPU.h"
#include "dawn/native/webgpu/Serialization.h"
#include "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));
}
Texture::Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor)
: TextureBase(device, descriptor),
RecordableObject(schema::ObjectType::Texture),
ObjectWGPU(device->wgpu.textureRelease) {
wgpu::TextureUsage actualUsage = 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;
}
std::vector<WGPUTextureFormat> viewFormats;
viewFormats.reserve(GetViewFormats().size());
for (FormatIndex i : GetViewFormats()) {
viewFormats.push_back(ToAPI(device->GetValidInternalFormat(i).format));
}
WGPUTextureDescriptor desc = {
.nextInChain = nullptr,
.label = ToOutputStringView(GetLabel()),
.usage = ToAPI(actualUsage),
.dimension = ToAPI(GetDimension()),
.size = ToWGPU(GetBaseSize()),
.format = ToAPI(GetFormat().format),
.mipLevelCount = GetNumMipLevels(),
.sampleCount = GetSampleCount(),
.viewFormatCount = viewFormats.size(),
.viewFormats = viewFormats.data(),
};
mInnerHandle = device->wgpu.deviceCreateTexture(device->GetInnerHandle(), &desc);
DAWN_ASSERT(mInnerHandle);
}
void Texture::DestroyImpl(DestroyReason reason) {
TextureBase::DestroyImpl(reason);
auto& wgpu = ToBackend(GetDevice())->wgpu;
wgpu.textureDestroy(mInnerHandle);
}
void Texture::SetLabelImpl() {
ToBackend(GetDevice())->CaptureSetLabel(this, GetLabel());
}
// 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,
}};
Serialize(captureContext, tex);
return {};
}
// 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 we don't need to capture it.
if (!IsInitialized() || !newResource) {
return {};
}
struct MapAsyncResult {
WGPUMapAsyncStatus status;
std::string message;
} mapAsyncResult = {};
// TODO(413053623): Support depth/stencil and multi-planar textures.
// Also, this can't handle compressed textures on compat as they are not readable (no copyT2B)
const TypedTexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(Aspect::Color).block;
WGPUBuffer copyBuffer = captureContext.GetCopyBuffer();
Device* device = ToBackend(GetDevice());
WGPUDevice innerDevice = device->GetInnerHandle();
WGPUQueue queue = ToBackend(device->GetQueue())->GetInnerHandle();
auto& wgpu = device->wgpu;
// For each mip level, emit a WriteTexture command, for the entire level.
// Then, 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::Color));
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 = wgpu::TextureAspect::All,
}},
.layout = {{
.offset = 0,
.bytesPerRow = usedBytesPerRow,
.rowsPerImage = uint32_t(blockSize.height),
}},
.size = {{
.width = uint32_t(size.width),
.height = uint32_t(size.height),
.depthOrArrayLayers = 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{0});
for (BlockCount z{0}; z < blockSize.depthOrArrayLayers; ++z) {
for (BlockCount y{0}; 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 = uint32_t(blockInfo.ToTexelHeight(y)),
.z = uint32_t(blockInfo.ToTexelHeight(z)),
},
.aspect = WGPUTextureAspect_All,
};
WGPUTexelCopyBufferInfo dstBuffer{
.layout =
{
.offset = 0,
.bytesPerRow = alignedBytesPerRow,
.rowsPerImage = uint32_t(blockRows),
},
.buffer = copyBuffer,
};
WGPUExtent3D copySize{
.width = uint32_t(blockInfo.ToTexelWidth(blockSize.width)),
.height = uint32_t(blockInfo.ToTexelHeight(blockRows)),
.depthOrArrayLayers = 1,
};
WGPUCommandEncoder encoder = wgpu.deviceCreateCommandEncoder(innerDevice, nullptr);
wgpu.commandEncoderCopyTextureToBuffer(encoder, &srcTexture, &dstBuffer, &copySize);
WGPUCommandBuffer commandBuffer = wgpu.commandEncoderFinish(encoder, nullptr);
wgpu.queueSubmit(queue, 1, &commandBuffer);
wgpu.commandBufferRelease(commandBuffer);
wgpu.commandEncoderRelease(encoder);
// 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;
innerCallbackInfo.userdata2 = this;
// 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{0}; blockRow < blockRows; ++blockRow) {
const void* data = wgpu.bufferGetConstMappedRange(
copyBuffer, uint32_t(blockRow) * alignedBytesPerRow, mappableBytesPerRow);
writer.WriteContentBytes(data, usedBytesPerRow);
}
wgpu.bufferUnmap(copyBuffer);
}
}
}
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