blob: 1f587c9b645b9cc27f4c4e7f812b4110acee8cd7 [file] [log] [blame] [edit]
//
// Copyright 2024 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include "libANGLE/renderer/wgpu/wgpu_helpers.h"
#include <algorithm>
#include "common/PackedGLEnums_autogen.h"
#include "dawn/dawn_proc_table.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/wgpu/ContextWgpu.h"
#include "libANGLE/renderer/wgpu/DisplayWgpu.h"
#include "libANGLE/renderer/wgpu/FramebufferWgpu.h"
#include "libANGLE/renderer/wgpu/wgpu_utils.h"
#include "webgpu/webgpu.h"
namespace rx
{
namespace webgpu
{
namespace
{
WGPUTextureDescriptor TextureDescriptorFromTexture(const DawnProcTable *wgpu,
const webgpu::TextureHandle &texture)
{
WGPUTextureDescriptor descriptor = WGPU_TEXTURE_DESCRIPTOR_INIT;
descriptor.usage = wgpu->textureGetUsage(texture.get());
descriptor.dimension = wgpu->textureGetDimension(texture.get());
descriptor.size = {wgpu->textureGetWidth(texture.get()), wgpu->textureGetHeight(texture.get()),
wgpu->textureGetDepthOrArrayLayers(texture.get())};
descriptor.format = wgpu->textureGetFormat(texture.get());
descriptor.mipLevelCount = wgpu->textureGetMipLevelCount(texture.get());
descriptor.sampleCount = wgpu->textureGetSampleCount(texture.get());
descriptor.viewFormatCount = 0;
return descriptor;
}
size_t GetSafeBufferMapOffset(size_t offset)
{
static_assert(gl::isPow2(kBufferMapOffsetAlignment));
return roundDownPow2(offset, kBufferMapOffsetAlignment);
}
size_t GetSafeBufferMapSize(size_t offset, size_t size)
{
// The offset is rounded down for alignment and the size is rounded up. The safe size must cover
// both of these offsets.
size_t offsetChange = offset % kBufferMapOffsetAlignment;
static_assert(gl::isPow2(kBufferMapSizeAlignment));
return roundUpPow2(size + offsetChange, kBufferMapSizeAlignment);
}
uint8_t *AdjustMapPointerForOffset(uint8_t *mapPtr, size_t offset)
{
// Fix up a map pointer that has been adjusted for alignment
size_t offsetChange = offset % kBufferMapOffsetAlignment;
return mapPtr + offsetChange;
}
const uint8_t *AdjustMapPointerForOffset(const uint8_t *mapPtr, size_t offset)
{
return AdjustMapPointerForOffset(const_cast<uint8_t *>(mapPtr), offset);
}
angle::Result GetCheckedAllocationSizeAndRowPitch(uint32_t rowBytes,
uint32_t width,
uint32_t height,
uint32_t *outputRowPitchOut,
uint32_t *allocationSizeOut)
{
angle::CheckedNumeric<uint32_t> checkedOutputRowPitch = rx::CheckedRoundUp(
static_cast<uint32_t>(angle::base::CheckMul(rowBytes, width).ValueOrDie()),
static_cast<uint32_t>(webgpu::kTextureRowSizeAlignment));
angle::CheckedNumeric<uint32_t> checkedAllocationSize = rx::CheckedRoundUpPow2(
static_cast<uint32_t>(angle::base::CheckMul(checkedOutputRowPitch, height).ValueOrDie()),
static_cast<uint32_t>(kBufferSizeAlignment));
if (!checkedAllocationSize.AssignIfValid(allocationSizeOut) ||
!checkedOutputRowPitch.AssignIfValid(outputRowPitchOut))
{
ERR() << "Calculating allocation size and output row pitch for an image copy failed.";
return angle::Result::Stop;
}
return angle::Result::Continue;
}
} // namespace
ImageHelper::ImageHelper() {}
ImageHelper::~ImageHelper() {}
angle::Result ImageHelper::initImage(const DawnProcTable *wgpu,
angle::FormatID intendedFormatID,
angle::FormatID actualFormatID,
DeviceHandle device,
gl::LevelIndex firstAllocatedLevel,
WGPUTextureDescriptor textureDescriptor)
{
mProcTable = wgpu;
mIntendedFormatID = intendedFormatID;
mActualFormatID = actualFormatID;
mTextureDescriptor = textureDescriptor;
mFirstAllocatedLevel = firstAllocatedLevel;
mTexture =
TextureHandle::Acquire(wgpu, wgpu->deviceCreateTexture(device.get(), &mTextureDescriptor));
mInitialized = true;
return angle::Result::Continue;
}
angle::Result ImageHelper::initExternal(const DawnProcTable *wgpu,
angle::FormatID intendedFormatID,
angle::FormatID actualFormatID,
webgpu::TextureHandle externalTexture)
{
mProcTable = wgpu;
mIntendedFormatID = intendedFormatID;
mActualFormatID = actualFormatID;
mTextureDescriptor = TextureDescriptorFromTexture(wgpu, externalTexture);
mFirstAllocatedLevel = gl::LevelIndex(0);
mTexture = externalTexture;
mInitialized = true;
return angle::Result::Continue;
}
angle::Result ImageHelper::flushStagedUpdates(ContextWgpu *contextWgpu)
{
if (mSubresourceQueue.empty())
{
return angle::Result::Continue;
}
for (gl::LevelIndex currentMipLevel = mFirstAllocatedLevel;
currentMipLevel < mFirstAllocatedLevel + getLevelCount(); ++currentMipLevel)
{
ANGLE_TRY(flushSingleLevelUpdates(contextWgpu, currentMipLevel, nullptr, 0));
}
return angle::Result::Continue;
}
angle::Result ImageHelper::flushSingleLevelUpdates(ContextWgpu *contextWgpu,
gl::LevelIndex levelGL,
ClearValuesArray *deferredClears,
uint32_t deferredClearIndex)
{
std::vector<SubresourceUpdate> *currentLevelQueue = getLevelUpdates(levelGL);
if (!currentLevelQueue || currentLevelQueue->empty())
{
return angle::Result::Continue;
}
std::optional<CommandEncoderHandle> encoder;
auto ensureEncoder = [&]() -> angle::Result {
ANGLE_TRY(contextWgpu->getCurrentCommandEncoder(
webgpu::RenderPassClosureReason::CopyBufferToTexture, &encoder.emplace()));
return angle::Result::Continue;
};
WGPUTexelCopyTextureInfo dst = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
dst.texture = mTexture.get();
std::vector<PackedRenderPassColorAttachment> colorAttachments;
TextureViewHandle textureView;
// Create a texture view of the entire level, layers and all.
ANGLE_TRY(createTextureView(levelGL, /*levelCount=*/1, /*layerIndex*/ 0,
mTextureDescriptor.size.depthOrArrayLayers, textureView,
WGPUTextureViewDimension_Undefined));
bool updateDepth = false;
bool updateStencil = false;
float depthValue = 1;
uint32_t stencilValue = 0;
for (const SubresourceUpdate &srcUpdate : *currentLevelQueue)
{
if (!isTextureLevelInAllocatedImage(srcUpdate.targetLevel))
{
continue;
}
switch (srcUpdate.updateSource)
{
case UpdateSource::Texture:
{
LevelIndex wgpuLevel = toWgpuLevel(srcUpdate.targetLevel);
dst.mipLevel = wgpuLevel.get();
WGPUExtent3D copyExtent = getLevelSize(wgpuLevel);
// TODO(anglebug.com/389145696): copyExtent just always copies to the whole level.
// Should support smaller regions.
dst.origin = WGPUOrigin3D{0, 0, srcUpdate.layerIndex};
// Updating multiple layers at once maybe not currently supported.
ASSERT(srcUpdate.layerCount == 1);
copyExtent.depthOrArrayLayers = srcUpdate.layerCount;
if (!encoder.has_value())
{
ANGLE_TRY(ensureEncoder());
}
WGPUTexelCopyBufferInfo copyInfo = WGPU_TEXEL_COPY_BUFFER_INFO_INIT;
copyInfo.layout = srcUpdate.textureDataLayout;
copyInfo.buffer = srcUpdate.textureData.get();
mProcTable->commandEncoderCopyBufferToTexture(encoder.value().get(), &copyInfo,
&dst, &copyExtent);
}
break;
case UpdateSource::Clear:
if (deferredClears)
{
if (deferredClearIndex == kUnpackedDepthIndex)
{
if (srcUpdate.clearData.hasStencil)
{
deferredClears->store(kUnpackedStencilIndex,
srcUpdate.clearData.clearValues);
}
if (!srcUpdate.clearData.hasDepth)
{
break;
}
}
deferredClears->store(deferredClearIndex, srcUpdate.clearData.clearValues);
}
else
{
colorAttachments.push_back(CreateNewClearColorAttachment(
srcUpdate.clearData.clearValues.clearColor,
srcUpdate.clearData.clearValues.depthSlice, textureView));
if (srcUpdate.clearData.hasDepth)
{
updateDepth = true;
depthValue = srcUpdate.clearData.clearValues.depthValue;
}
if (srcUpdate.clearData.hasStencil)
{
updateStencil = true;
stencilValue = srcUpdate.clearData.clearValues.stencilValue;
}
}
break;
}
}
FramebufferWgpu *frameBuffer =
GetImplAs<FramebufferWgpu>(contextWgpu->getState().getDrawFramebuffer());
if (!colorAttachments.empty())
{
frameBuffer->addNewColorAttachments(colorAttachments);
}
if (updateDepth || updateStencil)
{
frameBuffer->updateDepthStencilAttachment(CreateNewDepthStencilAttachment(
depthValue, stencilValue, textureView, updateDepth, updateStencil));
}
currentLevelQueue->clear();
return angle::Result::Continue;
}
WGPUTextureDescriptor ImageHelper::createTextureDescriptor(WGPUTextureUsage usage,
WGPUTextureDimension dimension,
WGPUExtent3D size,
WGPUTextureFormat format,
std::uint32_t mipLevelCount,
std::uint32_t sampleCount)
{
WGPUTextureDescriptor textureDescriptor = WGPU_TEXTURE_DESCRIPTOR_INIT;
textureDescriptor.usage = usage;
textureDescriptor.dimension = dimension;
textureDescriptor.size = size;
textureDescriptor.format = format;
textureDescriptor.mipLevelCount = mipLevelCount;
textureDescriptor.sampleCount = sampleCount;
textureDescriptor.viewFormatCount = 0;
return textureDescriptor;
}
angle::Result ImageHelper::stageTextureUpload(ContextWgpu *contextWgpu,
const webgpu::Format &webgpuFormat,
GLenum type,
const gl::Extents &glExtents,
GLuint inputRowPitch,
GLuint inputDepthPitch,
uint32_t outputRowPitch,
uint32_t outputDepthPitch,
uint32_t allocationSize,
const gl::ImageIndex &index,
const uint8_t *pixels)
{
if (pixels == nullptr)
{
return angle::Result::Continue;
}
mProcTable = webgpu::GetProcs(contextWgpu);
webgpu::DeviceHandle device = contextWgpu->getDevice();
gl::LevelIndex levelGL(index.getLevelIndex());
BufferHelper bufferHelper;
WGPUBufferUsage usage = WGPUBufferUsage_CopySrc | WGPUBufferUsage_CopyDst;
ANGLE_TRY(
bufferHelper.initBuffer(mProcTable, device, allocationSize, usage, MapAtCreation::Yes));
LoadImageFunctionInfo loadFunctionInfo = webgpuFormat.getTextureLoadFunction(type);
uint8_t *data = bufferHelper.getMapWritePointer(0, allocationSize);
loadFunctionInfo.loadFunction(contextWgpu->getImageLoadContext(), glExtents.width,
glExtents.height, glExtents.depth, pixels, inputRowPitch,
inputDepthPitch, data, outputRowPitch, outputDepthPitch);
ANGLE_TRY(bufferHelper.unmap());
WGPUTexelCopyBufferLayout textureDataLayout = WGPU_TEXEL_COPY_BUFFER_LAYOUT_INIT;
textureDataLayout.bytesPerRow = outputRowPitch;
textureDataLayout.rowsPerImage = outputDepthPitch;
GLint layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
appendSubresourceUpdate(
levelGL,
SubresourceUpdate(UpdateSource::Texture, levelGL, layerIndex, index.getLayerCount(),
bufferHelper.getBuffer(), textureDataLayout));
return angle::Result::Continue;
}
void ImageHelper::stageClear(gl::LevelIndex targetLevel,
ClearValues clearValues,
bool hasDepth,
bool hasStencil)
{
appendSubresourceUpdate(targetLevel, SubresourceUpdate(UpdateSource::Clear, targetLevel,
clearValues, hasDepth, hasStencil));
}
void ImageHelper::removeSingleSubresourceStagedUpdates(gl::LevelIndex levelToRemove,
uint32_t layerIndex,
uint32_t layerCount)
{
std::vector<SubresourceUpdate> *updatesToClear = getLevelUpdates(levelToRemove);
if (!updatesToClear || updatesToClear->size() == 0)
{
return;
}
for (int64_t i = updatesToClear->size() - 1; i >= 0; i--)
{
size_t index = static_cast<size_t>(i);
// TODO(anglebug.com/420782526): maybe clear partial matches here. Vulkan backend does not.
if ((*updatesToClear)[index].layerIndex == layerIndex &&
(*updatesToClear)[index].layerCount == layerCount)
{
updatesToClear->erase(updatesToClear->begin() + index);
}
}
}
void ImageHelper::removeStagedUpdates(gl::LevelIndex levelToRemove)
{
std::vector<SubresourceUpdate> *updateToClear = getLevelUpdates(levelToRemove);
if (updateToClear)
{
updateToClear->clear();
}
}
void ImageHelper::resetImage()
{
if (mTexture)
{
mProcTable->textureDestroy(mTexture.get());
}
mProcTable = nullptr;
mTexture = nullptr;
mTextureDescriptor = WGPU_TEXTURE_DESCRIPTOR_INIT;
mInitialized = false;
mFirstAllocatedLevel = gl::LevelIndex(0);
}
angle::Result ImageHelper::CopyImage(ContextWgpu *contextWgpu,
ImageHelper *srcImage,
const gl::ImageIndex &dstIndex,
const gl::Offset &dstOffset,
gl::LevelIndex sourceLevelGL,
uint32_t sourceLayer,
const gl::Box &sourceBox)
{
gl::LevelIndex dstLevel(dstIndex.getLevelIndex());
uint32_t dstLayerOrZOffset = dstIndex.hasLayer() ? dstIndex.getLayerIndex() : dstOffset.z;
WGPUTexelCopyTextureInfo src = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
src.texture = srcImage->getTexture().get();
src.mipLevel = srcImage->toWgpuLevel(sourceLevelGL).get();
src.origin.x = static_cast<uint32_t>(sourceBox.x);
src.origin.y = static_cast<uint32_t>(sourceBox.y);
src.origin.z = sourceLayer;
WGPUTexelCopyTextureInfo dst = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
dst.texture = mTexture.get();
dst.mipLevel = toWgpuLevel(dstLevel).get();
dst.origin.x = static_cast<uint32_t>(dstOffset.x);
dst.origin.y = static_cast<uint32_t>(dstOffset.y);
dst.origin.z = dstLayerOrZOffset;
WGPUExtent3D copySize = {static_cast<uint32_t>(sourceBox.width),
static_cast<uint32_t>(sourceBox.height),
static_cast<uint32_t>(sourceBox.depth)};
webgpu::CommandEncoderHandle encoder;
ANGLE_TRY(contextWgpu->getCurrentCommandEncoder(
webgpu::RenderPassClosureReason::CopyTextureToTexture, &encoder));
mProcTable->commandEncoderCopyTextureToTexture(encoder.get(), &src, &dst, &copySize);
return angle::Result::Continue;
}
angle::Result ImageHelper::copyImageCpuReadback(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Rectangle &sourceArea,
const gl::Offset &dstOffset,
const gl::Extents &dstExtent,
const gl::InternalFormat &formatInfo,
ImageHelper *srcImage,
const gl::Extents &srcExtents)
{
ContextWgpu *contextWgpu = GetImpl(context);
mProcTable = webgpu::GetProcs(contextWgpu);
const webgpu::Format &dstWebgpuFormat = contextWgpu->getFormat(formatInfo.sizedInternalFormat);
const angle::Format &storageFormat = dstWebgpuFormat.getActualImageFormat();
LoadImageFunctionInfo loadFunctionInfo =
dstWebgpuFormat.getTextureLoadFunction(formatInfo.type);
uint32_t allocationSize;
uint32_t outputRowPitch;
ANGLE_TRY(GetCheckedAllocationSizeAndRowPitch(storageFormat.pixelBytes, sourceArea.width,
sourceArea.height, &outputRowPitch,
&allocationSize));
BufferHelper stagingBuffer;
ANGLE_TRY(stagingBuffer.initBuffer(mProcTable, contextWgpu->getDevice(), allocationSize,
WGPUBufferUsage_CopySrc | WGPUBufferUsage_CopyDst,
MapAtCreation::Yes));
uint8_t *stagingPointer = stagingBuffer.getMapWritePointer(0, allocationSize);
const angle::Format &copyFormat =
GetFormatFromFormatType(formatInfo.internalFormat, formatInfo.type);
PackPixelsParams packPixelsParams(sourceArea, copyFormat, static_cast<GLuint>(outputRowPitch),
false, nullptr, 0);
if (loadFunctionInfo.requiresConversion)
{
// When a conversion is required, we need to use the loadFunction to read from a temporary
// buffer instead so its an even slower path.
angle::MemoryBuffer memoryBuffer;
if (!memoryBuffer.resize(allocationSize))
{
return angle::Result::Stop;
}
// Read into the scratch buffer
ANGLE_TRY(srcImage->readPixels(contextWgpu, sourceArea, packPixelsParams,
srcImage->toWgpuLevel(gl::LevelIndex(0)), 0,
memoryBuffer.data()));
// Load from scratch buffer to our pixel buffer
loadFunctionInfo.loadFunction(contextWgpu->getImageLoadContext(), sourceArea.width,
sourceArea.height, 1, memoryBuffer.data(),
packPixelsParams.outputPitch, 0, stagingPointer,
outputRowPitch, 0);
}
else
{
// We read directly from the framebuffer into our pixel buffer.
ANGLE_TRY(srcImage->readPixels(contextWgpu, sourceArea, packPixelsParams,
srcImage->toWgpuLevel(gl::LevelIndex(0)), 0,
stagingPointer));
}
ANGLE_TRY(stagingBuffer.unmap());
WGPUTexelCopyBufferLayout textureDataLayout = WGPU_TEXEL_COPY_BUFFER_LAYOUT_INIT;
textureDataLayout.bytesPerRow = outputRowPitch;
textureDataLayout.rowsPerImage = sourceArea.height;
textureDataLayout.offset =
dstOffset.x * storageFormat.pixelBytes + dstOffset.y * outputRowPitch;
gl::LevelIndex levelGL(index.getLevelIndex());
GLint layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
appendSubresourceUpdate(
levelGL,
SubresourceUpdate(UpdateSource::Texture, levelGL, layerIndex, index.getLayerCount(),
stagingBuffer.getBuffer(), textureDataLayout));
return angle::Result::Continue;
}
// static
angle::Result ImageHelper::getReadPixelsParams(rx::ContextWgpu *contextWgpu,
const gl::PixelPackState &packState,
gl::Buffer *packBuffer,
GLenum format,
GLenum type,
const gl::Rectangle &area,
const gl::Rectangle &clippedArea,
rx::PackPixelsParams *paramsOut,
GLuint *skipBytesOut)
{
const gl::InternalFormat &sizedFormatInfo = gl::GetInternalFormatInfo(format, type);
GLuint outputPitch = 0;
ANGLE_CHECK_GL_MATH(contextWgpu,
sizedFormatInfo.computeRowPitch(type, area.width, packState.alignment,
packState.rowLength, &outputPitch));
ANGLE_CHECK_GL_MATH(contextWgpu, sizedFormatInfo.computeSkipBytes(
type, outputPitch, 0, packState, false, skipBytesOut));
ANGLE_TRY(GetPackPixelsParams(sizedFormatInfo, outputPitch, packState, packBuffer, area,
clippedArea, paramsOut, skipBytesOut));
return angle::Result::Continue;
}
angle::Result ImageHelper::readPixels(rx::ContextWgpu *contextWgpu,
const gl::Rectangle &area,
const rx::PackPixelsParams &packPixelsParams,
webgpu::LevelIndex level,
uint32_t layer,
void *pixels)
{
if (mActualFormatID == angle::FormatID::NONE)
{
// Unimplemented texture format
UNIMPLEMENTED();
return angle::Result::Stop;
}
DeviceHandle device = contextWgpu->getDisplay()->getDevice();
webgpu::CommandEncoderHandle encoder;
ANGLE_TRY(contextWgpu->getCurrentCommandEncoder(
webgpu::RenderPassClosureReason::CopyBufferToTexture, &encoder));
const angle::Format &actualFormat = angle::Format::Get(mActualFormatID);
uint32_t textureBytesPerRow =
roundUp(actualFormat.pixelBytes * area.width, kCopyBufferAlignment);
WGPUTexelCopyBufferLayout textureDataLayout = WGPU_TEXEL_COPY_BUFFER_LAYOUT_INIT;
textureDataLayout.bytesPerRow = textureBytesPerRow;
textureDataLayout.rowsPerImage = area.height;
size_t allocationSize = textureBytesPerRow * area.height;
BufferHelper bufferHelper;
ANGLE_TRY(bufferHelper.initBuffer(mProcTable, device, allocationSize,
WGPUBufferUsage_MapRead | WGPUBufferUsage_CopyDst,
MapAtCreation::No));
WGPUTexelCopyBufferInfo copyBuffer = WGPU_TEXEL_COPY_BUFFER_INFO_INIT;
copyBuffer.buffer = bufferHelper.getBuffer().get();
copyBuffer.layout = textureDataLayout;
WGPUTexelCopyTextureInfo copyTexture WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
copyTexture.origin.x = area.x;
copyTexture.origin.y = area.y;
copyTexture.origin.z = layer;
copyTexture.texture = mTexture.get();
copyTexture.mipLevel = level.get();
WGPUExtent3D copySize = WGPU_EXTENT_3D_INIT;
copySize.width = area.width;
copySize.height = area.height;
mProcTable->commandEncoderCopyTextureToBuffer(encoder.get(), &copyTexture, &copyBuffer,
&copySize);
ANGLE_TRY(contextWgpu->flush(webgpu::RenderPassClosureReason::GLReadPixels));
ANGLE_TRY(bufferHelper.mapImmediate(contextWgpu, WGPUMapMode_Read, 0, allocationSize));
const uint8_t *readPixelBuffer = bufferHelper.getMapReadPointer(0, allocationSize);
PackPixels(packPixelsParams, actualFormat, textureBytesPerRow, readPixelBuffer,
static_cast<uint8_t *>(pixels));
return angle::Result::Continue;
}
angle::Result ImageHelper::createTextureViewSingleLevel(gl::LevelIndex targetLevel,
uint32_t layerIndex,
TextureViewHandle &textureViewOut)
{
return createTextureView(targetLevel, /*levelCount=*/1, layerIndex, /*arrayLayerCount=*/1,
textureViewOut, WGPUTextureViewDimension_Undefined);
}
angle::Result ImageHelper::createFullTextureView(TextureViewHandle &textureViewOut,
WGPUTextureViewDimension desiredViewDimension)
{
return createTextureView(mFirstAllocatedLevel, mTextureDescriptor.mipLevelCount, 0,
mTextureDescriptor.size.depthOrArrayLayers, textureViewOut,
desiredViewDimension);
}
angle::Result ImageHelper::createTextureView(
gl::LevelIndex targetLevel,
uint32_t levelCount,
uint32_t layerIndex,
uint32_t arrayLayerCount,
TextureViewHandle &textureViewOut,
Optional<WGPUTextureViewDimension> desiredViewDimension)
{
if (!isTextureLevelInAllocatedImage(targetLevel))
{
return angle::Result::Stop;
}
WGPUTextureViewDescriptor textureViewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
textureViewDesc.aspect = WGPUTextureAspect_All;
textureViewDesc.baseArrayLayer = layerIndex;
textureViewDesc.arrayLayerCount = arrayLayerCount;
textureViewDesc.baseMipLevel = toWgpuLevel(targetLevel).get();
textureViewDesc.mipLevelCount = levelCount;
if (!desiredViewDimension.valid())
{
switch (mTextureDescriptor.dimension)
{
case WGPUTextureDimension_Undefined:
textureViewDesc.dimension = WGPUTextureViewDimension_Undefined;
break;
case WGPUTextureDimension_1D:
textureViewDesc.dimension = WGPUTextureViewDimension_1D;
break;
case WGPUTextureDimension_2D:
textureViewDesc.dimension = WGPUTextureViewDimension_2D;
break;
case WGPUTextureDimension_3D:
textureViewDesc.dimension = WGPUTextureViewDimension_3D;
break;
default:
UNIMPLEMENTED();
return angle::Result::Stop;
}
}
else
{
textureViewDesc.dimension = desiredViewDimension.value();
}
textureViewDesc.format = mTextureDescriptor.format;
textureViewOut = TextureViewHandle::Acquire(
mProcTable, mProcTable->textureCreateView(mTexture.get(), &textureViewDesc));
return angle::Result::Continue;
}
gl::LevelIndex ImageHelper::getLastAllocatedLevel() const
{
return mFirstAllocatedLevel + mTextureDescriptor.mipLevelCount - 1;
}
LevelIndex ImageHelper::toWgpuLevel(gl::LevelIndex levelIndexGl) const
{
return gl_wgpu::getLevelIndex(levelIndexGl, mFirstAllocatedLevel);
}
gl::LevelIndex ImageHelper::toGlLevel(LevelIndex levelIndexWgpu) const
{
return wgpu_gl::GetLevelIndex(levelIndexWgpu, mFirstAllocatedLevel);
}
bool ImageHelper::isTextureLevelInAllocatedImage(gl::LevelIndex textureLevel) const
{
if (!mInitialized || textureLevel < mFirstAllocatedLevel)
{
return false;
}
LevelIndex wgpuTextureLevel = toWgpuLevel(textureLevel);
return wgpuTextureLevel < LevelIndex(mTextureDescriptor.mipLevelCount);
}
WGPUExtent3D ImageHelper::getLevelSize(LevelIndex wgpuLevel) const
{
WGPUExtent3D copyExtent = mTextureDescriptor.size;
// https://www.w3.org/TR/webgpu/#abstract-opdef-logical-miplevel-specific-texture-extent
copyExtent.width = std::max(1u, copyExtent.width >> wgpuLevel.get());
copyExtent.height = std::max(1u, copyExtent.height >> wgpuLevel.get());
if (mTextureDescriptor.dimension == WGPUTextureDimension_3D)
{
copyExtent.depthOrArrayLayers =
std::max(1u, copyExtent.depthOrArrayLayers >> wgpuLevel.get());
}
return copyExtent;
}
void ImageHelper::appendSubresourceUpdate(gl::LevelIndex level, SubresourceUpdate &&update)
{
if (mSubresourceQueue.size() <= static_cast<size_t>(level.get()))
{
mSubresourceQueue.resize(level.get() + 1);
}
mSubresourceQueue[level.get()].emplace_back(std::move(update));
onStateChange(angle::SubjectMessage::SubjectChanged);
}
std::vector<SubresourceUpdate> *ImageHelper::getLevelUpdates(gl::LevelIndex level)
{
return static_cast<size_t>(level.get()) < mSubresourceQueue.size()
? &mSubresourceQueue[level.get()]
: nullptr;
}
BufferHelper::BufferHelper() {}
BufferHelper::~BufferHelper() {}
void BufferHelper::reset()
{
mProcTable = nullptr;
mBuffer = nullptr;
mMappedState.reset();
}
angle::Result BufferHelper::initBuffer(const DawnProcTable *wgpu,
webgpu::DeviceHandle device,
size_t size,
WGPUBufferUsage usage,
MapAtCreation mappedAtCreation)
{
mProcTable = wgpu;
size_t safeBufferSize = rx::roundUpPow2(size, kBufferSizeAlignment);
WGPUBufferDescriptor descriptor = WGPU_BUFFER_DESCRIPTOR_INIT;
descriptor.size = safeBufferSize;
descriptor.usage = usage;
descriptor.mappedAtCreation = mappedAtCreation == MapAtCreation::Yes;
mBuffer = webgpu::BufferHandle::Acquire(
mProcTable, mProcTable->deviceCreateBuffer(device.get(), &descriptor));
if (mappedAtCreation == MapAtCreation::Yes)
{
mMappedState = {WGPUMapMode_Read | WGPUMapMode_Write, 0, safeBufferSize};
}
else
{
mMappedState.reset();
}
mRequestedSize = size;
return angle::Result::Continue;
}
angle::Result BufferHelper::mapImmediate(ContextWgpu *context,
WGPUMapMode mode,
size_t offset,
size_t size)
{
ASSERT(!mMappedState.has_value());
WGPUMapAsyncStatus mapResult = WGPUMapAsyncStatus_Error;
WGPUBufferMapCallbackInfo mapAsyncCallback = WGPU_BUFFER_MAP_CALLBACK_INFO_INIT;
mapAsyncCallback.mode = WGPUCallbackMode_WaitAnyOnly;
mapAsyncCallback.callback = [](WGPUMapAsyncStatus status, struct WGPUStringView message,
void *userdata1, void *userdata2) {
WGPUMapAsyncStatus *pStatus = reinterpret_cast<WGPUMapAsyncStatus *>(userdata1);
ASSERT(userdata2 == nullptr);
*pStatus = status;
};
mapAsyncCallback.userdata1 = &mapResult;
size_t safeBufferMapOffset = GetSafeBufferMapOffset(offset);
size_t safeBufferMapSize = GetSafeBufferMapSize(offset, size);
WGPUFutureWaitInfo waitInfo;
waitInfo.future = mProcTable->bufferMapAsync(mBuffer.get(), mode, safeBufferMapOffset,
safeBufferMapSize, mapAsyncCallback);
webgpu::InstanceHandle instance = context->getDisplay()->getInstance();
ANGLE_WGPU_TRY(context, mProcTable->instanceWaitAny(instance.get(), 1, &waitInfo, -1));
ANGLE_WGPU_TRY(context, mapResult);
ASSERT(waitInfo.completed);
mMappedState = {mode, safeBufferMapOffset, safeBufferMapSize};
return angle::Result::Continue;
}
angle::Result BufferHelper::unmap()
{
if (mMappedState.has_value())
{
mProcTable->bufferUnmap(mBuffer.get());
mMappedState.reset();
}
return angle::Result::Continue;
}
uint8_t *BufferHelper::getMapWritePointer(size_t offset, size_t size) const
{
ASSERT(mProcTable->bufferGetMapState(mBuffer.get()) == WGPUBufferMapState_Mapped);
ASSERT(mMappedState.has_value());
ASSERT(mMappedState->offset <= offset);
ASSERT(mMappedState->offset + mMappedState->size >= offset + size);
void *mapPtr = mProcTable->bufferGetMappedRange(mBuffer.get(), GetSafeBufferMapOffset(offset),
GetSafeBufferMapSize(offset, size));
ASSERT(mapPtr);
return AdjustMapPointerForOffset(static_cast<uint8_t *>(mapPtr), offset);
}
const uint8_t *BufferHelper::getMapReadPointer(size_t offset, size_t size) const
{
ASSERT(mProcTable->bufferGetMapState(mBuffer.get()) == WGPUBufferMapState_Mapped);
ASSERT(mMappedState.has_value());
ASSERT(mMappedState->offset <= offset);
ASSERT(mMappedState->offset + mMappedState->size >= offset + size);
// wgpuBufferGetConstMappedRange is used for reads whereas wgpuBufferGetMappedRange is only used
// for writes.
const void *mapPtr = mProcTable->bufferGetConstMappedRange(
mBuffer.get(), GetSafeBufferMapOffset(offset), GetSafeBufferMapSize(offset, size));
ASSERT(mapPtr);
return AdjustMapPointerForOffset(static_cast<const uint8_t *>(mapPtr), offset);
}
const std::optional<BufferMapState> &BufferHelper::getMappedState() const
{
return mMappedState;
}
bool BufferHelper::canMapForRead() const
{
return (mMappedState.has_value() && (mMappedState->mode & WGPUMapMode_Read)) ||
(mBuffer && (mProcTable->bufferGetUsage(mBuffer.get()) & WGPUBufferUsage_MapRead));
}
bool BufferHelper::canMapForWrite() const
{
return (mMappedState.has_value() && (mMappedState->mode & WGPUMapMode_Write)) ||
(mBuffer && (mProcTable->bufferGetUsage(mBuffer.get()) & WGPUBufferUsage_MapWrite));
}
bool BufferHelper::isMappedForRead() const
{
return mMappedState.has_value() && (mMappedState->mode & WGPUMapMode_Read);
}
bool BufferHelper::isMappedForWrite() const
{
return mMappedState.has_value() && (mMappedState->mode & WGPUMapMode_Write);
}
webgpu::BufferHandle BufferHelper::getBuffer() const
{
return mBuffer;
}
uint64_t BufferHelper::requestedSize() const
{
return mRequestedSize;
}
uint64_t BufferHelper::actualSize() const
{
return mBuffer ? mProcTable->bufferGetSize(mBuffer.get()) : 0;
}
angle::Result BufferHelper::readDataImmediate(ContextWgpu *context,
size_t offset,
size_t size,
webgpu::RenderPassClosureReason reason,
BufferReadback *result)
{
ASSERT(result);
if (getMappedState())
{
ANGLE_TRY(unmap());
}
// Create a staging buffer just big enough for this copy but aligned for both copying and
// mapping.
const size_t stagingBufferSize = roundUpPow2(
size, std::max(webgpu::kBufferCopyToBufferAlignment, webgpu::kBufferMapOffsetAlignment));
ANGLE_TRY(result->buffer.initBuffer(
mProcTable, context->getDisplay()->getDevice(), stagingBufferSize,
WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead, webgpu::MapAtCreation::No));
// Copy the source buffer to staging and flush the commands
webgpu::CommandEncoderHandle commandEncoder;
ANGLE_TRY(context->getCurrentCommandEncoder(reason, &commandEncoder));
size_t safeCopyOffset = rx::roundDownPow2(offset, webgpu::kBufferCopyToBufferAlignment);
size_t offsetAdjustment = offset - safeCopyOffset;
size_t copySize = roundUpPow2(size + offsetAdjustment, webgpu::kBufferCopyToBufferAlignment);
mProcTable->commandEncoderCopyBufferToBuffer(commandEncoder.get(), mBuffer.get(),
safeCopyOffset, result->buffer.getBuffer().get(),
0, copySize);
ANGLE_TRY(context->flush(reason));
// Read back from the staging buffer and compute the index range
ANGLE_TRY(result->buffer.mapImmediate(context, WGPUMapMode_Read, offsetAdjustment, size));
result->data = result->buffer.getMapReadPointer(offsetAdjustment, size);
return angle::Result::Continue;
}
} // namespace webgpu
} // namespace rx