blob: 35db99d34834c56eec2cef3d62a4e20281d6d7d4 [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.
//
// TextureWgpu.cpp:
// Implements the class methods for TextureWgpu.
//
#include "libANGLE/renderer/wgpu/TextureWgpu.h"
#include "common/PackedGLEnums_autogen.h"
#include "common/debug.h"
#include "libANGLE/Error.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/wgpu/ContextWgpu.h"
#include "libANGLE/renderer/wgpu/DisplayWgpu.h"
#include "libANGLE/renderer/wgpu/FramebufferWgpu.h"
#include "libANGLE/renderer/wgpu/ImageWgpu.h"
#include "libANGLE/renderer/wgpu/RenderTargetWgpu.h"
#include "libANGLE/renderer/wgpu/UtilsWgpu.h"
#include "libANGLE/renderer/wgpu/wgpu_utils.h"
namespace rx
{
namespace
{
constexpr angle::SubjectIndex kTextureImageSubjectIndex = 0;
void GetRenderTargetLayerCountAndIndex(webgpu::ImageHelper *image,
const gl::ImageIndex &index,
GLuint *layerIndex,
GLuint *layerCount,
GLuint *imageLayerCount)
{
*layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
*layerCount = index.getLayerCount();
switch (index.getType())
{
case gl::TextureType::_2D:
case gl::TextureType::_2DMultisample:
case gl::TextureType::External:
ASSERT(*layerIndex == 0 &&
(*layerCount == 1 ||
*layerCount == static_cast<GLuint>(gl::ImageIndex::kEntireLevel)));
*imageLayerCount = 1;
break;
case gl::TextureType::CubeMap:
ASSERT(!index.hasLayer() ||
*layerIndex == static_cast<GLuint>(index.cubeMapFaceIndex()));
*imageLayerCount = gl::kCubeFaceCount;
break;
case gl::TextureType::_3D:
{
gl::LevelIndex levelGL(index.getLevelIndex());
*imageLayerCount = image->getTextureDescriptor().size.depthOrArrayLayers;
break;
}
case gl::TextureType::_2DArray:
case gl::TextureType::_2DMultisampleArray:
case gl::TextureType::CubeMapArray:
// NOTE: Not yet supported, should set *imageLayerCount.
UNIMPLEMENTED();
break;
default:
UNREACHABLE();
}
if (*layerCount == static_cast<GLuint>(gl::ImageIndex::kEntireLevel))
{
ASSERT(*layerIndex == 0);
*layerCount = *imageLayerCount;
}
}
bool IsTextureLevelDefinitionCompatibleWithImage(webgpu::ImageHelper *image,
const gl::Extents &size,
const webgpu::Format &format)
{
return size == wgpu_gl::GetExtents(image->getSize()) &&
image->getIntendedFormatID() == format.getIntendedFormatID() &&
image->getActualFormatID() == format.getActualImageFormatID();
}
bool CanCopyWithTransferForTexImage(const webgpu::ImageHelper &srcImage,
angle::FormatID dstIntendedFormatID,
angle::FormatID dstActualFormatID,
WGPUTextureUsage dstUsage,
bool isViewportFlipY)
{
// For glTex[Sub]Image, only accept same-format transfers.
// There are cases that two images' actual format is the same, but intended formats are
// different due to one is using the fallback format (for example, RGB fallback to RGBA). In
// these situations CanCopyWithTransfer will say yes. But if we use transfer to do copy, the
// alpha channel will be also be copied with source data which is wrong.
bool isFormatCompatible = srcImage.getIntendedFormatID() == dstIntendedFormatID &&
srcImage.getActualFormatID() == dstActualFormatID;
return !isViewportFlipY && isFormatCompatible &&
(srcImage.getUsage() & WGPUTextureUsage_CopySrc) != 0 &&
(dstUsage & WGPUTextureUsage_CopyDst) != 0;
}
bool CanCopyWithDraw(const webgpu::ImageHelper &srcImage, WGPUTextureUsage dstUsage)
{
return (srcImage.getUsage() & WGPUTextureUsage_TextureBinding) != 0 &&
(dstUsage & WGPUTextureUsage_RenderAttachment) != 0;
}
bool CanCopyWithTransferForCopyTexture(ContextWgpu *contextWgpu,
const webgpu::ImageHelper &srcImage,
angle::FormatID destIntendedFormatID,
angle::FormatID destActualFormatID,
WGPUTextureUsage destUsage,
bool unpackFlipY,
bool unpackPremultiplyAlpha,
bool unpackUnmultiplyAlpha)
{
if (unpackFlipY || unpackPremultiplyAlpha != unpackUnmultiplyAlpha)
{
return false;
}
bool isFormatCompatible = srcImage.getIntendedFormatID() == destIntendedFormatID &&
srcImage.getActualFormatID() == destActualFormatID;
return isFormatCompatible && (srcImage.getUsage() & WGPUTextureUsage_CopySrc) != 0 &&
(destUsage & WGPUTextureUsage_CopyDst) != 0;
}
} // namespace
TextureWgpu::TextureWgpu(const gl::TextureState &state)
: TextureImpl(state),
mCurrentBaseLevel(state.getBaseLevel()),
mCurrentMaxLevel(state.getMaxLevel()),
mImageObserverBinding(this, kTextureImageSubjectIndex)
{
setImageHelper(new webgpu::ImageHelper(), true);
}
TextureWgpu::~TextureWgpu() {}
void TextureWgpu::onDestroy(const gl::Context *context)
{
setImageHelper(nullptr, true);
}
angle::Result TextureWgpu::setImage(const gl::Context *context,
const gl::ImageIndex &index,
GLenum internalFormat,
const gl::Extents &size,
GLenum format,
GLenum type,
const gl::PixelUnpackState &unpack,
gl::Buffer *unpackBuffer,
const uint8_t *pixels)
{
return setImageImpl(context, internalFormat, type, index, size, unpack, pixels);
}
angle::Result TextureWgpu::setSubImage(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Box &area,
GLenum format,
GLenum type,
const gl::PixelUnpackState &unpack,
gl::Buffer *unpackBuffer,
const uint8_t *pixels)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(format, type);
return setSubImageImpl(context, contextWgpu->getFormat(formatInfo.sizedInternalFormat), type,
index, area, unpack, pixels);
}
angle::Result TextureWgpu::setCompressedImage(const gl::Context *context,
const gl::ImageIndex &index,
GLenum internalFormat,
const gl::Extents &size,
const gl::PixelUnpackState &unpack,
size_t imageSize,
const uint8_t *pixels)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::setCompressedSubImage(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Box &area,
GLenum format,
const gl::PixelUnpackState &unpack,
size_t imageSize,
const uint8_t *pixels)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::copyImage(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Rectangle &sourceArea,
GLenum internalFormat,
gl::Framebuffer *source)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
gl::Extents newImageSize(sourceArea.width, sourceArea.height, 1);
const gl::InternalFormat &internalFormatInfo =
gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);
const webgpu::Format &webgpuFormat =
contextWgpu->getFormat(internalFormatInfo.sizedInternalFormat);
// The texture level being redefined might be the same as the one bound to the framebuffer.
// This _could_ be supported by using a temp image before redefining the level (and potentially
// discarding the image). However, this is currently unimplemented.
FramebufferWgpu *framebufferWgpu = GetImplAs<FramebufferWgpu>(source);
RenderTargetWgpu *colorReadRT = framebufferWgpu->getReadPixelsRenderTarget();
webgpu::ImageHelper *srcImage = colorReadRT->getImage();
const bool isCubeMap = index.getType() == gl::TextureType::CubeMap;
gl::LevelIndex levelIndex(index.getLevelIndex());
const uint32_t layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
const uint32_t redefinedFace = isCubeMap ? layerIndex : 0;
const uint32_t sourceFace = isCubeMap ? colorReadRT->getLayer() : 0;
const bool isSelfCopy = mImage->getTexture().get() == srcImage->getTexture().get() &&
levelIndex == colorReadRT->getGlLevel() && redefinedFace == sourceFace;
// TODO(anglebug.com/438268609): handle copying a texture when it needs to be redefined.
ANGLE_TRY(redefineLevel(context, webgpuFormat, index, newImageSize));
// TODO(crbug.com/438268609): Remove this and implement path to initialize the destination image
// then stage a copy update.
if (!mImage->isInitialized())
{
ANGLE_TRY(initializeImageImpl(contextWgpu, webgpuFormat, index.getLevelIndex() + 1,
gl::LevelIndex(mState.getEffectiveBaseLevel()),
newImageSize));
}
if (isSelfCopy)
{
UNIMPLEMENTED();
return angle::Result::Continue;
}
return copySubImageImpl(context, index, gl::Offset(0, 0, 0), sourceArea, internalFormatInfo,
source);
}
angle::Result TextureWgpu::copySubImage(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Offset &destOffset,
const gl::Rectangle &sourceArea,
gl::Framebuffer *source)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
const gl::InternalFormat &internalFormat = *mState.getImageDesc(index).format.info;
const webgpu::Format &webgpuFormat = contextWgpu->getFormat(internalFormat.sizedInternalFormat);
// TODO(crbug.com/438268609): Remove this and implement path to initialize the destination image
// then stage a copy update.
if (!mImage->isInitialized())
{
ANGLE_TRY(initializeImageImpl(contextWgpu, webgpuFormat, index.getLevelIndex() + 1,
gl::LevelIndex(mState.getEffectiveBaseLevel()),
mState.getBaseLevelDesc().size));
}
return copySubImageImpl(context, index, destOffset, sourceArea, internalFormat, source);
}
angle::Result TextureWgpu::copySubImageImpl(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Offset &destOffset,
const gl::Rectangle &sourceArea,
const gl::InternalFormat &internalFormat,
gl::Framebuffer *source)
{
gl::Extents fbSize = source->getReadColorAttachment()->getSize();
gl::Rectangle clippedSourceArea;
if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
&clippedSourceArea))
{
return angle::Result::Continue;
}
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
FramebufferWgpu *framebufferWgpu = GetImplAs<FramebufferWgpu>(source);
// If negative offsets are given, clippedSourceArea ensures we don't read from those offsets.
// However, that changes the sourceOffset->destOffset mapping. Here, destOffset is shifted by
// the same amount as clipped to correct the error.
WGPUTextureDimension imageType = gl_wgpu::GetWgpuTextureDimension(mState.getType());
int zOffset = (imageType == WGPUTextureDimension_3D) ? destOffset.z : 0;
const gl::Offset modifiedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x,
destOffset.y + clippedSourceArea.y - sourceArea.y, zOffset);
RenderTargetWgpu *colorReadRT = framebufferWgpu->getReadPixelsRenderTarget();
if (!colorReadRT || !colorReadRT->getImage())
{
UNIMPLEMENTED();
return angle::Result::Continue;
}
const webgpu::Format &dstFormat = contextWgpu->getFormat(internalFormat.sizedInternalFormat);
angle::FormatID dstIntendedFormatID = dstFormat.getIntendedFormatID();
angle::FormatID dstActualFormatID = dstFormat.getActualImageFormatID();
bool isViewportFlipY = framebufferWgpu->flipY();
gl::Box clippedSourceBox(clippedSourceArea.x, clippedSourceArea.y, colorReadRT->getLayer(),
clippedSourceArea.width, clippedSourceArea.height, 1);
// If it's possible to perform the copy with a transfer, that's the best option.
if (CanCopyWithTransferForTexImage(*colorReadRT->getImage(), dstIntendedFormatID,
dstActualFormatID, mImage->getUsage(), isViewportFlipY))
{
return mImage->CopyImage(contextWgpu, colorReadRT->getImage(), index, modifiedDestOffset,
colorReadRT->getGlLevel(), colorReadRT->getLayer(),
clippedSourceBox);
}
// If it's possible to perform the copy with a draw call, do that.
if (CanCopyWithDraw(*colorReadRT->getImage(), mImage->getUsage()))
{
webgpu::TextureViewHandle dstView;
ANGLE_TRY(mImage->createTextureViewSingleLevel(gl::LevelIndex(index.getLevelIndex()),
index.hasLayer() ? index.getLayerIndex() : 0,
dstView));
WGPUExtent3D srcSize = colorReadRT->getImage()->getTextureDescriptor().size;
WGPUExtent3D dstSize = mImage->getTextureDescriptor().size;
const angle::Format &srcFormat =
angle::Format::Get(colorReadRT->getImage()->getIntendedFormatID());
const angle::Format &dstAngleFormat = dstFormat.getIntendedFormat();
gl::Rectangle finalSourceArea = clippedSourceArea;
return contextWgpu->getUtils()->copyImage(
contextWgpu, colorReadRT->getTextureView(), dstView, finalSourceArea,
modifiedDestOffset, srcSize, dstSize, false, false, isViewportFlipY, false, srcFormat,
dstAngleFormat.id, dstActualFormatID);
}
return getImage()->copyImageCpuReadback(
context, index, clippedSourceArea, modifiedDestOffset,
gl::Extents(clippedSourceArea.width, clippedSourceArea.height, 1), internalFormat,
colorReadRT->getImage(), source->getExtents());
}
angle::Result TextureWgpu::copyTexture(const gl::Context *context,
const gl::ImageIndex &index,
GLenum internalFormat,
GLenum type,
GLint sourceLevel,
bool unpackFlipY,
bool unpackPremultiplyAlpha,
bool unpackUnmultiplyAlpha,
const gl::Texture *source)
{
ContextWgpu *contextWgpu = webgpu::GetImpl(context);
TextureWgpu *sourceTextureWgpu = webgpu::GetImpl(source);
const gl::ImageDesc &srcImageDesc = sourceTextureWgpu->mState.getImageDesc(
NonCubeTextureTypeToTarget(source->getType()), sourceLevel);
const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
const webgpu::Format &dstWebgpuFormat =
contextWgpu->getFormat(internalFormatInfo.sizedInternalFormat);
ANGLE_TRY(redefineLevel(context, dstWebgpuFormat, index, srcImageDesc.size));
// TODO(crbug.com/438268609): Remove this and implement path to initialize the destination image
// then stage a copy update.
if (!mImage->isInitialized())
{
ANGLE_TRY(initializeImageImpl(contextWgpu, dstWebgpuFormat, index.getLevelIndex() + 1,
gl::LevelIndex(sourceLevel), srcImageDesc.size));
}
return copySubTextureImpl(context, index, gl::kOffsetZero, sourceLevel,
gl::Box(gl::kOffsetZero, srcImageDesc.size), unpackFlipY,
unpackPremultiplyAlpha, unpackUnmultiplyAlpha, dstWebgpuFormat,
internalFormatInfo, sourceTextureWgpu);
}
angle::Result TextureWgpu::copySubTexture(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Offset &destOffset,
GLint sourceLevel,
const gl::Box &sourceBox,
bool unpackFlipY,
bool unpackPremultiplyAlpha,
bool unpackUnmultiplyAlpha,
const gl::Texture *source)
{
ContextWgpu *contextWgpu = webgpu::GetImpl(context);
TextureWgpu *sourceTextureWgpu = webgpu::GetImpl(source);
gl::TextureTarget target = index.getTarget();
gl::LevelIndex dstLevelGL(index.getLevelIndex());
const gl::InternalFormat &internalFormat =
*mState.getImageDesc(target, dstLevelGL.get()).format.info;
const webgpu::Format &dstWebgpuFormat =
contextWgpu->getFormat(internalFormat.sizedInternalFormat);
const gl::ImageDesc &srcImageDesc = sourceTextureWgpu->mState.getImageDesc(
NonCubeTextureTypeToTarget(source->getType()), sourceLevel);
// TODO(crbug.com/438268609): Remove this and implement path to initialize the destination image
// then stage a copy update.
if (!mImage->isInitialized())
{
ANGLE_TRY(initializeImageImpl(contextWgpu, dstWebgpuFormat, index.getLevelIndex() + 1,
gl::LevelIndex(sourceLevel), srcImageDesc.size));
mImage->removeStagedUpdates(dstLevelGL);
}
return copySubTextureImpl(context, index, destOffset, sourceLevel, sourceBox, unpackFlipY,
unpackPremultiplyAlpha, unpackUnmultiplyAlpha, dstWebgpuFormat,
internalFormat, sourceTextureWgpu);
}
angle::Result TextureWgpu::copySubTextureImpl(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Offset &destOffset,
GLint sourceLevel,
const gl::Box &sourceBox,
bool unpackFlipY,
bool unpackPremultiplyAlpha,
bool unpackUnmultiplyAlpha,
const webgpu::Format &dstWebgpuFormat,
const gl::InternalFormat &internalFormat,
TextureWgpu *sourceTextureWgpu)
{
ContextWgpu *contextWgpu = webgpu::GetImpl(context);
// We need to ensure both that the source texture is initialized and any updates to it are
// staged so we are not copying from a blank texture.
ANGLE_TRY(sourceTextureWgpu->ensureImageInitialized(context));
ANGLE_TRY(sourceTextureWgpu->getImage()->flushStagedUpdates(contextWgpu));
if (CanCopyWithTransferForCopyTexture(
contextWgpu, *sourceTextureWgpu->getImage(), dstWebgpuFormat.getIntendedFormatID(),
dstWebgpuFormat.getActualImageFormatID(), mImage->getUsage(), unpackFlipY,
unpackPremultiplyAlpha, unpackUnmultiplyAlpha))
{
return mImage->CopyImage(contextWgpu, sourceTextureWgpu->getImage(), index, destOffset,
gl::LevelIndex(sourceLevel), static_cast<uint32_t>(sourceBox.z),
sourceBox);
}
if (CanCopyWithDraw(*sourceTextureWgpu->getImage(), mImage->getUsage()))
{
webgpu::TextureViewHandle srcView;
ANGLE_TRY(sourceTextureWgpu->getImage()->createTextureViewSingleLevel(
gl::LevelIndex(sourceLevel), 0, srcView));
webgpu::TextureViewHandle dstView;
ANGLE_TRY(mImage->createTextureViewSingleLevel(gl::LevelIndex(index.getLevelIndex()), 0,
dstView));
WGPUExtent3D srcSize = sourceTextureWgpu->getImage()->getTextureDescriptor().size;
WGPUExtent3D dstSize = mImage->getTextureDescriptor().size;
const angle::Format &srcFormat =
sourceTextureWgpu->getBaseLevelFormat(contextWgpu).getIntendedFormat();
gl::Rectangle sourceRect(sourceBox.x, sourceBox.y, sourceBox.width, sourceBox.height);
return contextWgpu->getUtils()->copyImage(
contextWgpu, srcView, dstView, sourceRect, destOffset, srcSize, dstSize,
unpackPremultiplyAlpha, unpackUnmultiplyAlpha, false, unpackFlipY, srcFormat,
dstWebgpuFormat.getIntendedFormatID(), dstWebgpuFormat.getActualImageFormatID());
}
const gl::ImageDesc &srcImageDesc = sourceTextureWgpu->mState.getImageDesc(
NonCubeTextureTypeToTarget(sourceTextureWgpu->mState.getType()),
gl::LevelIndex(sourceLevel).get());
return mImage->copyImageCpuReadback(
context, index, gl::Rectangle(sourceBox.x, sourceBox.y, sourceBox.width, sourceBox.height),
destOffset, gl::Extents(sourceBox.width, sourceBox.height, sourceBox.depth), internalFormat,
sourceTextureWgpu->getImage(), srcImageDesc.size);
}
angle::Result TextureWgpu::copyRenderbufferSubData(const gl::Context *context,
const gl::Renderbuffer *srcBuffer,
GLint srcLevel,
GLint srcX,
GLint srcY,
GLint srcZ,
GLint dstLevel,
GLint dstX,
GLint dstY,
GLint dstZ,
GLsizei srcWidth,
GLsizei srcHeight,
GLsizei srcDepth)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::copyTextureSubData(const gl::Context *context,
const gl::Texture *srcTexture,
GLint srcLevel,
GLint srcX,
GLint srcY,
GLint srcZ,
GLint dstLevel,
GLint dstX,
GLint dstY,
GLint dstZ,
GLsizei srcWidth,
GLsizei srcHeight,
GLsizei srcDepth)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::copyCompressedTexture(const gl::Context *context,
const gl::Texture *source)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::setStorage(const gl::Context *context,
gl::TextureType type,
size_t levels,
GLenum internalFormat,
const gl::Extents &size)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::setStorageExternalMemory(const gl::Context *context,
gl::TextureType type,
size_t levels,
GLenum internalFormat,
const gl::Extents &size,
gl::MemoryObject *memoryObject,
GLuint64 offset,
GLbitfield createFlags,
GLbitfield usageFlags,
const void *imageCreateInfoPNext)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::setEGLImageTarget(const gl::Context *context,
gl::TextureType type,
egl::Image *image)
{
ImageWgpu *imageWgpu = webgpu::GetImpl(image);
setImageHelper(imageWgpu->getImage(), false);
ASSERT(mImage->isInitialized());
return angle::Result::Continue;
}
angle::Result TextureWgpu::setImageExternal(const gl::Context *context,
gl::TextureType type,
egl::Stream *stream,
const egl::Stream::GLTextureDescription &desc)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::generateMipmap(const gl::Context *context)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::setBaseLevel(const gl::Context *context, GLuint baseLevel)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::bindTexImage(const gl::Context *context, egl::Surface *surface)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::releaseTexImage(const gl::Context *context)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::syncState(const gl::Context *context,
const gl::Texture::DirtyBits &dirtyBits,
gl::Command source)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
ANGLE_TRY(respecifyImageStorageIfNecessary(contextWgpu, source));
const bool isGenerateMipmap = source == gl::Command::GenerateMipmap;
ANGLE_TRY(initializeImage(contextWgpu, isGenerateMipmap
? ImageMipLevels::FullMipChainForGenerateMipmap
: ImageMipLevels::EnabledLevels));
ANGLE_TRY(mImage->flushStagedUpdates(contextWgpu));
return angle::Result::Continue;
}
angle::Result TextureWgpu::setStorageMultisample(const gl::Context *context,
gl::TextureType type,
GLsizei samples,
GLint internalformat,
const gl::Extents &size,
bool fixedSampleLocations)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::initializeContents(const gl::Context *context,
GLenum binding,
const gl::ImageIndex &imageIndex)
{
return angle::Result::Continue;
}
angle::Result TextureWgpu::getAttachmentRenderTarget(const gl::Context *context,
GLenum binding,
const gl::ImageIndex &imageIndex,
GLsizei samples,
FramebufferAttachmentRenderTarget **rtOut)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
ANGLE_TRY(respecifyImageStorageIfNecessary(contextWgpu, gl::Command::Draw));
if (!mImage->isInitialized())
{
ANGLE_TRY(initializeImage(contextWgpu, ImageMipLevels::EnabledLevels));
}
GLuint layerIndex = 0, layerCount = 0, imageLayerCount = 0;
GetRenderTargetLayerCountAndIndex(mImage, imageIndex, &layerIndex, &layerCount,
&imageLayerCount);
// NOTE: Multisampling not yet supported
ASSERT(samples <= 1);
const gl::RenderToTextureImageIndex renderToTextureIndex =
gl::RenderToTextureImageIndex::Default;
if (layerCount == 1)
{
ANGLE_TRY(initSingleLayerRenderTargets(contextWgpu, imageLayerCount,
gl::LevelIndex(imageIndex.getLevelIndex()),
renderToTextureIndex));
std::vector<std::vector<RenderTargetWgpu>> &levelRenderTargets =
mSingleLayerRenderTargets[renderToTextureIndex];
ASSERT(imageIndex.getLevelIndex() < static_cast<int32_t>(levelRenderTargets.size()));
std::vector<RenderTargetWgpu> &layerRenderTargets =
levelRenderTargets[imageIndex.getLevelIndex()];
ASSERT(imageIndex.getLayerIndex() < static_cast<int32_t>(layerRenderTargets.size()));
*rtOut = &layerRenderTargets[layerIndex];
}
else
{
// Not yet supported.
UNIMPLEMENTED();
}
return angle::Result::Continue;
}
void TextureWgpu::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
{
ASSERT(index == kTextureImageSubjectIndex &&
(message == angle::SubjectMessage::SubjectChanged ||
message == angle::SubjectMessage::InitializationComplete));
// Forward the notification to the parent that the staging buffer changed.
onStateChange(message);
}
angle::Result TextureWgpu::ensureImageInitialized(const gl::Context *context)
{
return initializeImage(webgpu::GetImpl(context), ImageMipLevels::EnabledLevels);
}
void TextureWgpu::releaseOwnershipOfImage(const gl::Context *context)
{
mOwnsImage = false;
setImageHelper(nullptr, true);
}
angle::Result TextureWgpu::setImageImpl(const gl::Context *context,
GLenum internalFormat,
GLenum type,
const gl::ImageIndex &index,
const gl::Extents &size,
const gl::PixelUnpackState &unpack,
const uint8_t *pixels)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
const webgpu::Format &webgpuFormat =
contextWgpu->getFormat(internalFormatInfo.sizedInternalFormat);
ANGLE_TRY(redefineLevel(context, webgpuFormat, index, size));
return setSubImageImpl(context, webgpuFormat, type, index, gl::Box(gl::kOffsetZero, size),
unpack, pixels);
}
angle::Result TextureWgpu::setSubImageImpl(const gl::Context *context,
const webgpu::Format &webgpuFormat,
GLenum type,
const gl::ImageIndex &index,
const gl::Box &area,
const gl::PixelUnpackState &unpack,
const uint8_t *pixels)
{
ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
if (!webgpuFormat.valid())
{
UNIMPLEMENTED();
return angle::Result::Continue;
}
const gl::InternalFormat &inputInternalFormatInfo = webgpuFormat.getInternalFormatInfo(type);
gl::Extents glExtents = gl::Extents(area.width, area.height, area.depth);
GLuint inputRowPitch = 0;
ANGLE_CHECK_GL_MATH(contextWgpu, inputInternalFormatInfo.computeRowPitch(
type, glExtents.width, unpack.alignment, unpack.rowLength,
&inputRowPitch));
GLuint inputDepthPitch = 0;
ANGLE_CHECK_GL_MATH(
contextWgpu, inputInternalFormatInfo.computeDepthPitch(glExtents.height, unpack.imageHeight,
inputRowPitch, &inputDepthPitch));
const angle::Format &actualFormat = webgpuFormat.getActualImageFormat();
uint32_t outputRowPitch = roundUp(actualFormat.pixelBytes * glExtents.width,
static_cast<uint32_t>(webgpu::kTextureRowSizeAlignment));
uint32_t outputDepthPitch = outputRowPitch * glExtents.height;
uint32_t allocationSize = outputDepthPitch * glExtents.depth;
// TODO(anglebug.com/389145696): ignores area.x|y|z
ANGLE_TRY(mImage->stageTextureUpload(contextWgpu, webgpuFormat, type, glExtents, inputRowPitch,
inputDepthPitch, outputRowPitch, outputDepthPitch,
allocationSize, index, pixels));
return angle::Result::Continue;
}
angle::Result TextureWgpu::initializeImage(ContextWgpu *contextWgpu, ImageMipLevels mipLevels)
{
if (mImage->isInitialized())
{
return angle::Result::Continue;
}
const gl::ImageDesc *firstLevelDesc = &mState.getBaseLevelDesc();
uint32_t levelCount = getMipLevelCount(mipLevels);
gl::LevelIndex firstLevel = gl::LevelIndex(mState.getEffectiveBaseLevel());
const webgpu::Format &webgpuFormat = getBaseLevelFormat(contextWgpu);
return initializeImageImpl(contextWgpu, webgpuFormat, levelCount, firstLevel,
firstLevelDesc->size);
}
angle::Result TextureWgpu::initializeImageImpl(ContextWgpu *contextWgpu,
const webgpu::Format &webgpuFormat,
uint32_t levelCount,
gl::LevelIndex firstLevel,
const gl::Extents &size)
{
const DawnProcTable *wgpu = webgpu::GetProcs(contextWgpu);
DisplayWgpu *displayWgpu = contextWgpu->getDisplay();
WGPUExtent3D wgpuExtents = gl_wgpu::GetExtent3D(size);
WGPUTextureDimension textureDimension = gl_wgpu::GetWgpuTextureDimension(mState.getType());
WGPUTextureUsage textureUsage = WGPUTextureUsage_CopySrc | WGPUTextureUsage_CopyDst |
WGPUTextureUsage_RenderAttachment |
WGPUTextureUsage_TextureBinding;
if (mState.getType() == gl::TextureType::CubeMap)
{
ASSERT(wgpuExtents.depthOrArrayLayers == 1);
ASSERT(wgpuExtents.width == wgpuExtents.height);
wgpuExtents.depthOrArrayLayers = 6;
}
return mImage->initImage(
wgpu, webgpuFormat.getIntendedFormatID(), webgpuFormat.getActualImageFormatID(),
displayWgpu->getDevice(), firstLevel,
mImage->createTextureDescriptor(
textureUsage, textureDimension, wgpuExtents,
webgpu::GetWgpuTextureFormatFromFormatID(webgpuFormat.getActualImageFormatID()),
levelCount, 1));
}
angle::Result TextureWgpu::redefineLevel(const gl::Context *context,
const webgpu::Format &webgpuFormat,
const gl::ImageIndex &index,
const gl::Extents &size)
{
if (mImage != nullptr && mOwnsImage)
{
// If there are any staged changes for this index, we can remove them since we're going
// to override them with this call.
gl::LevelIndex levelIndexGL(index.getLevelIndex());
const uint32_t layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
if (index.hasLayer())
{
mImage->removeSingleSubresourceStagedUpdates(levelIndexGL, layerIndex,
index.getLayerCount());
}
else
{
mImage->removeStagedUpdates(levelIndexGL);
}
if (mImage->isInitialized())
{
TextureLevelAllocation levelAllocation =
mImage->isTextureLevelInAllocatedImage(levelIndexGL)
? TextureLevelAllocation::WithinAllocatedImage
: TextureLevelAllocation::OutsideAllocatedImage;
TextureLevelDefinition levelDefinition =
IsTextureLevelDefinitionCompatibleWithImage(mImage, size, webgpuFormat)
? TextureLevelDefinition::Compatible
: TextureLevelDefinition::Incompatible;
if (TextureRedefineLevel(levelAllocation, levelDefinition, mState.getImmutableFormat(),
mImage->getLevelCount(), layerIndex, index,
mImage->getFirstAllocatedLevel(), &mRedefinedLevels))
{
// TODO(anglebug.com/425449020): release any views or references to this
// image, including RenderTargets.
mImage->resetImage();
}
}
}
else
{
setImageHelper(new webgpu::ImageHelper, true);
}
return angle::Result::Continue;
}
uint32_t TextureWgpu::getMipLevelCount(ImageMipLevels mipLevels) const
{
switch (mipLevels)
{
// Returns level count from base to max that has been specified, i.e, enabled.
case ImageMipLevels::EnabledLevels:
return mState.getEnabledLevelCount();
// Returns all mipmap levels from base to max regardless if an image has been specified or
// not.
case ImageMipLevels::FullMipChainForGenerateMipmap:
return getMaxLevelCount() - mState.getEffectiveBaseLevel();
default:
UNREACHABLE();
return 0;
}
}
uint32_t TextureWgpu::getMaxLevelCount() const
{
// getMipmapMaxLevel will be 0 here if mipmaps are not used, so the levelCount is always +1.
return mState.getMipmapMaxLevel() + 1;
}
angle::Result TextureWgpu::respecifyImageStorageIfNecessary(ContextWgpu *contextWgpu,
gl::Command source)
{
ASSERT(mState.getBuffer().get() == nullptr);
// Before redefining the image for any reason, check to see if it's about to go through mipmap
// generation. In that case, drop every staged change for the subsequent mips after base, and
// make sure the image is created with the complete mip chain.
const bool isGenerateMipmap = source == gl::Command::GenerateMipmap;
if (isGenerateMipmap)
{
prepareForGenerateMipmap(contextWgpu);
}
// Set base and max level before initializing the image
ANGLE_TRY(maybeUpdateBaseMaxLevels(contextWgpu));
// It is possible for the image to have a single level (because it doesn't use mipmapping),
// then have more levels defined in it and mipmapping enabled. In that case, the image needs
// to be recreated.
bool isMipmapEnabledByMinFilter = false;
if (!isGenerateMipmap && mImage && mImage->isInitialized())
{
isMipmapEnabledByMinFilter =
mImage->getLevelCount() < getMipLevelCount(ImageMipLevels::EnabledLevels);
}
// If generating mipmaps and the image needs to be recreated (not full-mip already, or changed
// usage flags), make sure it's recreated.
if (isGenerateMipmap && mImage && mImage->isInitialized() &&
(!mState.getImmutableFormat() &&
mImage->getLevelCount() !=
getMipLevelCount(ImageMipLevels::FullMipChainForGenerateMipmap)))
{
ANGLE_TRY(mImage->flushStagedUpdates(contextWgpu));
mImage->resetImage();
}
// Also recreate the image if it's changed in usage, or if any of its levels are redefined and
// no update to base/max levels were done (otherwise the above call would have already taken
// care of this).
// TODO(liza): Respecify the image once copying images is supported.
if (TextureHasAnyRedefinedLevels(mRedefinedLevels) || isMipmapEnabledByMinFilter)
{
ANGLE_TRY(mImage->flushStagedUpdates(contextWgpu));
mImage->resetImage();
}
return angle::Result::Continue;
}
void TextureWgpu::prepareForGenerateMipmap(ContextWgpu *contextWgpu)
{
gl::LevelIndex baseLevel(mState.getEffectiveBaseLevel());
gl::LevelIndex maxLevel(mState.getMipmapMaxLevel());
// Remove staged updates to the range that's being respecified (which is all the mips except
// baseLevel).
gl::LevelIndex firstGeneratedLevel = baseLevel + 1;
for (GLuint levelToRemove = mState.getEffectiveBaseLevel();
levelToRemove < mState.getMipmapMaxLevel(); levelToRemove++)
{
mImage->removeStagedUpdates(gl::LevelIndex(levelToRemove));
}
TextureRedefineGenerateMipmapLevels(baseLevel, maxLevel, firstGeneratedLevel,
&mRedefinedLevels);
// If generating mipmap and base level is incompatibly redefined, the image is going to be
// recreated. Don't try to preserve the other mips.
if (IsTextureLevelRedefined(mRedefinedLevels, mState.getType(), baseLevel))
{
ASSERT(!mState.getImmutableFormat());
mImage->resetImage();
}
}
angle::Result TextureWgpu::maybeUpdateBaseMaxLevels(ContextWgpu *contextWgpu)
{
bool baseLevelChanged = mCurrentBaseLevel.get() != static_cast<GLint>(mState.getBaseLevel());
bool maxLevelChanged = mCurrentMaxLevel.get() != static_cast<GLint>(mState.getMaxLevel());
if (!maxLevelChanged && !baseLevelChanged)
{
return angle::Result::Continue;
}
gl::LevelIndex newBaseLevel = gl::LevelIndex(mState.getEffectiveBaseLevel());
gl::LevelIndex newMaxLevel = gl::LevelIndex(mState.getEffectiveMaxLevel());
ASSERT(newBaseLevel <= newMaxLevel);
if (!mImage->isInitialized())
{
return angle::Result::Continue;
}
if (mState.getImmutableFormat())
{
// For immutable texture, baseLevel/maxLevel should be a subset of the texture's actual
// number of mip levels. We don't need to respecify an image.
ASSERT(!baseLevelChanged || newBaseLevel >= mImage->getFirstAllocatedLevel());
ASSERT(!maxLevelChanged || newMaxLevel < gl::LevelIndex(mImage->getLevelCount()));
}
else if (!baseLevelChanged && (newMaxLevel <= mImage->getLastAllocatedLevel()))
{
// With a valid image, check if only changing the maxLevel to a subset of the texture's
// actual number of mip levels
ASSERT(maxLevelChanged);
}
else
{
// TODO(liza): Respecify the image once copying images is supported.
mImage->resetImage();
return angle::Result::Continue;
}
mCurrentBaseLevel = newBaseLevel;
mCurrentMaxLevel = newMaxLevel;
return angle::Result::Continue;
}
angle::Result TextureWgpu::initSingleLayerRenderTargets(
ContextWgpu *contextWgpu,
GLuint layerCount,
gl::LevelIndex levelIndex,
gl::RenderToTextureImageIndex renderToTextureIndex)
{
std::vector<std::vector<RenderTargetWgpu>> &allLevelsRenderTargets =
mSingleLayerRenderTargets[renderToTextureIndex];
if (allLevelsRenderTargets.size() <= static_cast<uint32_t>(levelIndex.get()))
{
allLevelsRenderTargets.resize(levelIndex.get() + 1);
}
std::vector<RenderTargetWgpu> &renderTargets = allLevelsRenderTargets[levelIndex.get()];
// Lazy init. Check if already initialized.
if (!renderTargets.empty())
{
return angle::Result::Continue;
}
// There are |layerCount| render targets, one for each layer
renderTargets.resize(layerCount);
for (uint32_t layerIndex = 0; layerIndex < layerCount; ++layerIndex)
{
webgpu::TextureViewHandle textureView;
ANGLE_TRY(mImage->createTextureViewSingleLevel(levelIndex, layerIndex, textureView));
renderTargets[layerIndex].set(mImage, textureView, mImage->toWgpuLevel(levelIndex),
layerIndex, mImage->toWgpuTextureFormat());
}
return angle::Result::Continue;
}
const webgpu::Format &TextureWgpu::getBaseLevelFormat(ContextWgpu *contextWgpu) const
{
const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc();
return contextWgpu->getFormat(baseLevelDesc.format.info->sizedInternalFormat);
}
void TextureWgpu::setImageHelper(webgpu::ImageHelper *imageHelper, bool ownsImageHelper)
{
if (mOwnsImage && mImage)
{
mImageObserverBinding.bind(nullptr);
SafeDelete(mImage);
}
mImage = imageHelper;
mOwnsImage = ownsImageHelper;
if (mImage)
{
mImageObserverBinding.bind(mImage);
}
onStateChange(angle::SubjectMessage::SubjectChanged);
}
} // namespace rx