blob: ab16c1e8435d699ce988b51ee4d8d2545352f676 [file] [log] [blame] [edit]
// Copyright 2017 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn/native/opengl/CommandBufferGL.h"
#include <algorithm>
#include <cstring>
#include <vector>
#include "dawn/native/BindGroup.h"
#include "dawn/native/BindGroupTracker.h"
#include "dawn/native/CommandEncoder.h"
#include "dawn/native/Commands.h"
#include "dawn/native/ExternalTexture.h"
#include "dawn/native/RenderBundle.h"
#include "dawn/native/VertexFormat.h"
#include "dawn/native/opengl/BufferGL.h"
#include "dawn/native/opengl/ComputePipelineGL.h"
#include "dawn/native/opengl/DeviceGL.h"
#include "dawn/native/opengl/Forward.h"
#include "dawn/native/opengl/PersistentPipelineStateGL.h"
#include "dawn/native/opengl/PipelineLayoutGL.h"
#include "dawn/native/opengl/RenderPipelineGL.h"
#include "dawn/native/opengl/SamplerGL.h"
#include "dawn/native/opengl/TextureGL.h"
#include "dawn/native/opengl/UtilsGL.h"
namespace dawn::native::opengl {
namespace {
GLenum IndexFormatType(wgpu::IndexFormat format) {
switch (format) {
case wgpu::IndexFormat::Uint16:
return GL_UNSIGNED_SHORT;
case wgpu::IndexFormat::Uint32:
return GL_UNSIGNED_INT;
case wgpu::IndexFormat::Undefined:
break;
}
UNREACHABLE();
}
bool Is1DOr2D(wgpu::TextureDimension dimension) {
return dimension == wgpu::TextureDimension::e1D || dimension == wgpu::TextureDimension::e2D;
}
GLenum VertexFormatType(wgpu::VertexFormat format) {
switch (format) {
case wgpu::VertexFormat::Uint8x2:
case wgpu::VertexFormat::Uint8x4:
case wgpu::VertexFormat::Unorm8x2:
case wgpu::VertexFormat::Unorm8x4:
return GL_UNSIGNED_BYTE;
case wgpu::VertexFormat::Sint8x2:
case wgpu::VertexFormat::Sint8x4:
case wgpu::VertexFormat::Snorm8x2:
case wgpu::VertexFormat::Snorm8x4:
return GL_BYTE;
case wgpu::VertexFormat::Uint16x2:
case wgpu::VertexFormat::Uint16x4:
case wgpu::VertexFormat::Unorm16x2:
case wgpu::VertexFormat::Unorm16x4:
return GL_UNSIGNED_SHORT;
case wgpu::VertexFormat::Sint16x2:
case wgpu::VertexFormat::Sint16x4:
case wgpu::VertexFormat::Snorm16x2:
case wgpu::VertexFormat::Snorm16x4:
return GL_SHORT;
case wgpu::VertexFormat::Float16x2:
case wgpu::VertexFormat::Float16x4:
return GL_HALF_FLOAT;
case wgpu::VertexFormat::Float32:
case wgpu::VertexFormat::Float32x2:
case wgpu::VertexFormat::Float32x3:
case wgpu::VertexFormat::Float32x4:
return GL_FLOAT;
case wgpu::VertexFormat::Uint32:
case wgpu::VertexFormat::Uint32x2:
case wgpu::VertexFormat::Uint32x3:
case wgpu::VertexFormat::Uint32x4:
return GL_UNSIGNED_INT;
case wgpu::VertexFormat::Sint32:
case wgpu::VertexFormat::Sint32x2:
case wgpu::VertexFormat::Sint32x3:
case wgpu::VertexFormat::Sint32x4:
return GL_INT;
default:
UNREACHABLE();
}
}
GLboolean VertexFormatIsNormalized(wgpu::VertexFormat format) {
switch (format) {
case wgpu::VertexFormat::Unorm8x2:
case wgpu::VertexFormat::Unorm8x4:
case wgpu::VertexFormat::Snorm8x2:
case wgpu::VertexFormat::Snorm8x4:
case wgpu::VertexFormat::Unorm16x2:
case wgpu::VertexFormat::Unorm16x4:
case wgpu::VertexFormat::Snorm16x2:
case wgpu::VertexFormat::Snorm16x4:
return GL_TRUE;
default:
return GL_FALSE;
}
}
bool VertexFormatIsInt(wgpu::VertexFormat format) {
switch (format) {
case wgpu::VertexFormat::Uint8x2:
case wgpu::VertexFormat::Uint8x4:
case wgpu::VertexFormat::Sint8x2:
case wgpu::VertexFormat::Sint8x4:
case wgpu::VertexFormat::Uint16x2:
case wgpu::VertexFormat::Uint16x4:
case wgpu::VertexFormat::Sint16x2:
case wgpu::VertexFormat::Sint16x4:
case wgpu::VertexFormat::Uint32:
case wgpu::VertexFormat::Uint32x2:
case wgpu::VertexFormat::Uint32x3:
case wgpu::VertexFormat::Uint32x4:
case wgpu::VertexFormat::Sint32:
case wgpu::VertexFormat::Sint32x2:
case wgpu::VertexFormat::Sint32x3:
case wgpu::VertexFormat::Sint32x4:
return true;
default:
return false;
}
}
// Vertex buffers and index buffers are implemented as part of an OpenGL VAO that
// corresponds to a VertexState. On the contrary in Dawn they are part of the global state.
// This means that we have to re-apply these buffers on a VertexState change.
class VertexStateBufferBindingTracker {
public:
void OnSetIndexBuffer(BufferBase* buffer) {
mIndexBufferDirty = true;
mIndexBuffer = ToBackend(buffer);
}
void OnSetVertexBuffer(VertexBufferSlot slot, BufferBase* buffer, uint64_t offset) {
mVertexBuffers[slot] = ToBackend(buffer);
mVertexBufferOffsets[slot] = offset;
mDirtyVertexBuffers.set(slot);
}
void OnSetPipeline(RenderPipelineBase* pipeline) {
if (mLastPipeline == pipeline) {
return;
}
mIndexBufferDirty = true;
mDirtyVertexBuffers |= pipeline->GetVertexBufferSlotsUsed();
mLastPipeline = pipeline;
}
void Apply(const OpenGLFunctions& gl) {
if (mIndexBufferDirty && mIndexBuffer != nullptr) {
gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->GetHandle());
mIndexBufferDirty = false;
}
for (VertexBufferSlot slot :
IterateBitSet(mDirtyVertexBuffers & mLastPipeline->GetVertexBufferSlotsUsed())) {
for (VertexAttributeLocation location :
IterateBitSet(ToBackend(mLastPipeline)->GetAttributesUsingVertexBuffer(slot))) {
const VertexAttributeInfo& attribute = mLastPipeline->GetAttribute(location);
GLuint attribIndex = static_cast<GLuint>(static_cast<uint8_t>(location));
GLuint buffer = mVertexBuffers[slot]->GetHandle();
uint64_t offset = mVertexBufferOffsets[slot];
const VertexBufferInfo& vertexBuffer = mLastPipeline->GetVertexBuffer(slot);
uint32_t components = GetVertexFormatInfo(attribute.format).componentCount;
GLenum formatType = VertexFormatType(attribute.format);
GLboolean normalized = VertexFormatIsNormalized(attribute.format);
gl.BindBuffer(GL_ARRAY_BUFFER, buffer);
if (VertexFormatIsInt(attribute.format)) {
gl.VertexAttribIPointer(
attribIndex, components, formatType, vertexBuffer.arrayStride,
reinterpret_cast<void*>(static_cast<intptr_t>(offset + attribute.offset)));
} else {
gl.VertexAttribPointer(
attribIndex, components, formatType, normalized, vertexBuffer.arrayStride,
reinterpret_cast<void*>(static_cast<intptr_t>(offset + attribute.offset)));
}
}
}
mDirtyVertexBuffers.reset();
}
private:
bool mIndexBufferDirty = false;
Buffer* mIndexBuffer = nullptr;
ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mDirtyVertexBuffers;
ityp::array<VertexBufferSlot, Buffer*, kMaxVertexBuffers> mVertexBuffers;
ityp::array<VertexBufferSlot, uint64_t, kMaxVertexBuffers> mVertexBufferOffsets;
RenderPipelineBase* mLastPipeline = nullptr;
};
class BindGroupTracker : public BindGroupTrackerBase<false, uint64_t> {
public:
void OnSetPipeline(RenderPipeline* pipeline) {
BindGroupTrackerBase::OnSetPipeline(pipeline);
mPipeline = pipeline;
}
void OnSetPipeline(ComputePipeline* pipeline) {
BindGroupTrackerBase::OnSetPipeline(pipeline);
mPipeline = pipeline;
}
void Apply(const OpenGLFunctions& gl) {
BeforeApply();
for (BindGroupIndex index : IterateBitSet(mDirtyBindGroupsObjectChangedOrIsDynamic)) {
ApplyBindGroup(gl, index, mBindGroups[index], mDynamicOffsets[index]);
}
AfterApply();
}
private:
void ApplyBindGroup(const OpenGLFunctions& gl,
BindGroupIndex groupIndex,
BindGroupBase* group,
const ityp::vector<BindingIndex, uint64_t>& dynamicOffsets) {
const auto& indices = ToBackend(mPipelineLayout)->GetBindingIndexInfo()[groupIndex];
for (BindingIndex bindingIndex{0}; bindingIndex < group->GetLayout()->GetBindingCount();
++bindingIndex) {
const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
if (bindingInfo.bindingType == BindingInfoType::Texture) {
TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
view->CopyIfNeeded();
}
}
for (BindingIndex bindingIndex{0}; bindingIndex < group->GetLayout()->GetBindingCount();
++bindingIndex) {
const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
switch (bindingInfo.bindingType) {
case BindingInfoType::Buffer: {
BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
GLuint buffer = ToBackend(binding.buffer)->GetHandle();
GLuint index = indices[bindingIndex];
GLuint offset = binding.offset;
if (bindingInfo.buffer.hasDynamicOffset) {
// Dynamic buffers are packed at the front of BindingIndices.
offset += dynamicOffsets[bindingIndex];
}
GLenum target;
switch (bindingInfo.buffer.type) {
case wgpu::BufferBindingType::Uniform:
target = GL_UNIFORM_BUFFER;
break;
case wgpu::BufferBindingType::Storage:
case kInternalStorageBufferBinding:
case wgpu::BufferBindingType::ReadOnlyStorage:
target = GL_SHADER_STORAGE_BUFFER;
break;
case wgpu::BufferBindingType::Undefined:
UNREACHABLE();
}
gl.BindBufferRange(target, index, buffer, offset, binding.size);
break;
}
case BindingInfoType::Sampler: {
Sampler* sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
GLuint samplerIndex = indices[bindingIndex];
for (PipelineGL::SamplerUnit unit :
mPipeline->GetTextureUnitsForSampler(samplerIndex)) {
// Only use filtering for certain texture units, because int
// and uint texture are only complete without filtering
if (unit.shouldUseFiltering) {
gl.BindSampler(unit.unit, sampler->GetFilteringHandle());
} else {
gl.BindSampler(unit.unit, sampler->GetNonFilteringHandle());
}
}
break;
}
case BindingInfoType::Texture: {
TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
GLuint handle = view->GetHandle();
GLenum target = view->GetGLTarget();
GLuint viewIndex = indices[bindingIndex];
for (auto unit : mPipeline->GetTextureUnitsForTextureView(viewIndex)) {
gl.ActiveTexture(GL_TEXTURE0 + unit);
gl.BindTexture(target, handle);
if (ToBackend(view->GetTexture())->GetGLFormat().format ==
GL_DEPTH_STENCIL) {
Aspect aspect = view->GetAspects();
ASSERT(HasOneBit(aspect));
switch (aspect) {
case Aspect::None:
case Aspect::Color:
case Aspect::CombinedDepthStencil:
case Aspect::Plane0:
case Aspect::Plane1:
UNREACHABLE();
case Aspect::Depth:
gl.TexParameteri(target, GL_DEPTH_STENCIL_TEXTURE_MODE,
GL_DEPTH_COMPONENT);
break;
case Aspect::Stencil:
gl.TexParameteri(target, GL_DEPTH_STENCIL_TEXTURE_MODE,
GL_STENCIL_INDEX);
break;
}
}
}
break;
}
case BindingInfoType::StorageTexture: {
TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
Texture* texture = ToBackend(view->GetTexture());
GLuint handle = texture->GetHandle();
GLuint imageIndex = indices[bindingIndex];
GLenum access;
switch (bindingInfo.storageTexture.access) {
case wgpu::StorageTextureAccess::WriteOnly:
access = GL_WRITE_ONLY;
break;
case wgpu::StorageTextureAccess::Undefined:
UNREACHABLE();
}
// OpenGL ES only supports either binding a layer or the entire
// texture in glBindImageTexture().
GLboolean isLayered;
if (view->GetLayerCount() == 1) {
isLayered = GL_FALSE;
} else if (texture->GetArrayLayers() == view->GetLayerCount()) {
isLayered = GL_TRUE;
} else {
UNREACHABLE();
}
gl.BindImageTexture(imageIndex, handle, view->GetBaseMipLevel(), isLayered,
view->GetBaseArrayLayer(), access,
texture->GetGLFormat().internalFormat);
texture->Touch();
break;
}
case BindingInfoType::ExternalTexture:
UNREACHABLE();
break;
}
}
}
PipelineGL* mPipeline = nullptr;
};
void ResolveMultisampledRenderTargets(const OpenGLFunctions& gl,
const BeginRenderPassCmd* renderPass) {
ASSERT(renderPass != nullptr);
GLuint readFbo = 0;
GLuint writeFbo = 0;
for (ColorAttachmentIndex i :
IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
if (renderPass->colorAttachments[i].resolveTarget != nullptr) {
if (readFbo == 0) {
ASSERT(writeFbo == 0);
gl.GenFramebuffers(1, &readFbo);
gl.GenFramebuffers(1, &writeFbo);
}
TextureView* colorView = ToBackend(renderPass->colorAttachments[i].view.Get());
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
colorView->BindToFramebuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0);
TextureView* resolveView =
ToBackend(renderPass->colorAttachments[i].resolveTarget.Get());
gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, writeFbo);
resolveView->BindToFramebuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0);
gl.BlitFramebuffer(0, 0, renderPass->width, renderPass->height, 0, 0, renderPass->width,
renderPass->height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
ToBackend(resolveView->GetTexture())->Touch();
}
}
gl.DeleteFramebuffers(1, &readFbo);
gl.DeleteFramebuffers(1, &writeFbo);
}
// OpenGL SPEC requires the source/destination region must be a region that is contained
// within srcImage/dstImage. Here the size of the image refers to the virtual size, while
// Dawn validates texture copy extent with the physical size, so we need to re-calculate the
// texture copy extent to ensure it should fit in the virtual size of the subresource.
Extent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy, const Extent3D& copySize) {
Extent3D validTextureCopyExtent = copySize;
const TextureBase* texture = textureCopy.texture.Get();
Extent3D virtualSizeAtLevel =
texture->GetMipLevelSingleSubresourceVirtualSize(textureCopy.mipLevel);
ASSERT(textureCopy.origin.x <= virtualSizeAtLevel.width);
ASSERT(textureCopy.origin.y <= virtualSizeAtLevel.height);
if (copySize.width > virtualSizeAtLevel.width - textureCopy.origin.x) {
ASSERT(texture->GetFormat().isCompressed);
validTextureCopyExtent.width = virtualSizeAtLevel.width - textureCopy.origin.x;
}
if (copySize.height > virtualSizeAtLevel.height - textureCopy.origin.y) {
ASSERT(texture->GetFormat().isCompressed);
validTextureCopyExtent.height = virtualSizeAtLevel.height - textureCopy.origin.y;
}
return validTextureCopyExtent;
}
bool TextureFormatIsSnorm(wgpu::TextureFormat format) {
return format == wgpu::TextureFormat::RGBA8Snorm || format == wgpu::TextureFormat::RG8Snorm ||
format == wgpu::TextureFormat::R8Snorm;
}
} // namespace
CommandBuffer::CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor)
: CommandBufferBase(encoder, descriptor) {}
MaybeError CommandBuffer::Execute() {
const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL();
auto LazyClearSyncScope = [](const SyncScopeResourceUsage& scope) -> MaybeError {
for (size_t i = 0; i < scope.textures.size(); i++) {
Texture* texture = ToBackend(scope.textures[i]);
// Clear subresources that are not render attachments. Render attachments will be
// cleared in RecordBeginRenderPass by setting the loadop to clear when the texture
// subresource has not been initialized before the render pass.
DAWN_TRY(scope.textureUsages[i].Iterate(
[&](const SubresourceRange& range, wgpu::TextureUsage usage) -> MaybeError {
if (usage & ~wgpu::TextureUsage::RenderAttachment) {
DAWN_TRY(texture->EnsureSubresourceContentInitialized(range));
}
return {};
}));
}
for (BufferBase* bufferBase : scope.buffers) {
ToBackend(bufferBase)->EnsureDataInitialized();
}
return {};
};
size_t nextComputePassNumber = 0;
size_t nextRenderPassNumber = 0;
Command type;
while (mCommands.NextCommandId(&type)) {
switch (type) {
case Command::BeginComputePass: {
mCommands.NextCommand<BeginComputePassCmd>();
for (const SyncScopeResourceUsage& scope :
GetResourceUsages().computePasses[nextComputePassNumber].dispatchUsages) {
DAWN_TRY(LazyClearSyncScope(scope));
}
DAWN_TRY(ExecuteComputePass());
nextComputePassNumber++;
break;
}
case Command::BeginRenderPass: {
auto* cmd = mCommands.NextCommand<BeginRenderPassCmd>();
DAWN_TRY(
LazyClearSyncScope(GetResourceUsages().renderPasses[nextRenderPassNumber]));
LazyClearRenderPassAttachments(cmd);
DAWN_TRY(ExecuteRenderPass(cmd));
nextRenderPassNumber++;
break;
}
case Command::CopyBufferToBuffer: {
CopyBufferToBufferCmd* copy = mCommands.NextCommand<CopyBufferToBufferCmd>();
if (copy->size == 0) {
// Skip no-op copies.
break;
}
ToBackend(copy->source)->EnsureDataInitialized();
ToBackend(copy->destination)
->EnsureDataInitializedAsDestination(copy->destinationOffset, copy->size);
gl.BindBuffer(GL_PIXEL_PACK_BUFFER, ToBackend(copy->source)->GetHandle());
gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, ToBackend(copy->destination)->GetHandle());
gl.CopyBufferSubData(GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER,
copy->sourceOffset, copy->destinationOffset, copy->size);
gl.BindBuffer(GL_PIXEL_PACK_BUFFER, 0);
gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
ToBackend(copy->source)->TrackUsage();
ToBackend(copy->destination)->TrackUsage();
break;
}
case Command::CopyBufferToTexture: {
CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>();
if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
copy->copySize.depthOrArrayLayers == 0) {
// Skip no-op copies.
continue;
}
auto& src = copy->source;
auto& dst = copy->destination;
Buffer* buffer = ToBackend(src.buffer.Get());
DAWN_INVALID_IF(
dst.aspect == Aspect::Stencil,
"Copies to stencil textures are unsupported on the OpenGL backend.");
buffer->EnsureDataInitialized();
SubresourceRange range = GetSubresourcesAffectedByCopy(dst, copy->copySize);
if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize,
dst.mipLevel)) {
dst.texture->SetIsSubresourceContentInitialized(true, range);
} else {
DAWN_TRY(ToBackend(dst.texture)->EnsureSubresourceContentInitialized(range));
}
gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->GetHandle());
TextureDataLayout dataLayout;
dataLayout.offset = 0;
dataLayout.bytesPerRow = src.bytesPerRow;
dataLayout.rowsPerImage = src.rowsPerImage;
DoTexSubImage(gl, dst, reinterpret_cast<void*>(src.offset), dataLayout,
copy->copySize);
gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
ToBackend(dst.texture)->Touch();
buffer->TrackUsage();
break;
}
case Command::CopyTextureToBuffer: {
CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>();
if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
copy->copySize.depthOrArrayLayers == 0) {
// Skip no-op copies.
continue;
}
auto& src = copy->source;
auto& dst = copy->destination;
auto& copySize = copy->copySize;
Texture* texture = ToBackend(src.texture.Get());
Buffer* buffer = ToBackend(dst.buffer.Get());
const Format& formatInfo = texture->GetFormat();
const GLFormat& format = texture->GetGLFormat();
GLenum target = texture->GetGLTarget();
// TODO(crbug.com/dawn/667): Implement validation in WebGPU/Compat to
// avoid this codepath. OpenGL does not support readback from non-renderable
// texture formats.
if (formatInfo.isCompressed ||
(TextureFormatIsSnorm(formatInfo.format) &&
GetDevice()->IsToggleEnabled(Toggle::DisableSnormRead))) {
UNREACHABLE();
}
buffer->EnsureDataInitializedAsDestination(copy);
SubresourceRange subresources = GetSubresourcesAffectedByCopy(src, copy->copySize);
DAWN_TRY(texture->EnsureSubresourceContentInitialized(subresources));
// The only way to move data from a texture to a buffer in GL is via
// glReadPixels with a pack buffer. Create a temporary FBO for the copy.
gl.BindTexture(target, texture->GetHandle());
GLuint readFBO = 0;
gl.GenFramebuffers(1, &readFBO);
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
const TexelBlockInfo& blockInfo = formatInfo.GetAspectInfo(src.aspect).block;
gl.BindBuffer(GL_PIXEL_PACK_BUFFER, buffer->GetHandle());
gl.PixelStorei(GL_PACK_ROW_LENGTH, dst.bytesPerRow / blockInfo.byteSize);
GLenum glAttachment;
GLenum glFormat;
GLenum glType;
switch (src.aspect) {
case Aspect::Color:
glAttachment = GL_COLOR_ATTACHMENT0;
glFormat = format.format;
glType = format.type;
break;
case Aspect::Depth:
glAttachment = GL_DEPTH_ATTACHMENT;
glFormat = GL_DEPTH_COMPONENT;
glType = GL_FLOAT;
break;
case Aspect::Stencil:
glAttachment = GL_STENCIL_ATTACHMENT;
glFormat = GL_STENCIL_INDEX;
glType = GL_UNSIGNED_BYTE;
break;
case Aspect::CombinedDepthStencil:
case Aspect::None:
case Aspect::Plane0:
case Aspect::Plane1:
UNREACHABLE();
}
uint8_t* offset = reinterpret_cast<uint8_t*>(static_cast<uintptr_t>(dst.offset));
switch (texture->GetDimension()) {
case wgpu::TextureDimension::e1D:
case wgpu::TextureDimension::e2D: {
if (texture->GetArrayLayers() == 1) {
gl.FramebufferTexture2D(GL_READ_FRAMEBUFFER, glAttachment, target,
texture->GetHandle(), src.mipLevel);
gl.ReadPixels(src.origin.x, src.origin.y, copySize.width,
copySize.height, glFormat, glType, offset);
break;
}
// Implementation for 2D array is the same as 3D.
[[fallthrough]];
}
case wgpu::TextureDimension::e3D: {
const uint64_t bytesPerImage = dst.bytesPerRow * dst.rowsPerImage;
for (uint32_t z = 0; z < copySize.depthOrArrayLayers; ++z) {
gl.FramebufferTextureLayer(GL_READ_FRAMEBUFFER, glAttachment,
texture->GetHandle(), src.mipLevel,
src.origin.z + z);
gl.ReadPixels(src.origin.x, src.origin.y, copySize.width,
copySize.height, glFormat, glType, offset);
offset += bytesPerImage;
}
break;
}
}
gl.PixelStorei(GL_PACK_ROW_LENGTH, 0);
gl.BindBuffer(GL_PIXEL_PACK_BUFFER, 0);
gl.DeleteFramebuffers(1, &readFBO);
buffer->TrackUsage();
break;
}
case Command::CopyTextureToTexture: {
CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>();
if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
copy->copySize.depthOrArrayLayers == 0) {
// Skip no-op copies.
continue;
}
auto& src = copy->source;
auto& dst = copy->destination;
// TODO(crbug.com/dawn/817): add workaround for the case that imageExtentSrc
// is not equal to imageExtentDst. For example when copySize fits in the virtual
// size of the source image but does not fit in the one of the destination
// image.
Extent3D copySize = ComputeTextureCopyExtent(dst, copy->copySize);
Texture* srcTexture = ToBackend(src.texture.Get());
Texture* dstTexture = ToBackend(dst.texture.Get());
SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize);
SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize);
DAWN_TRY(srcTexture->EnsureSubresourceContentInitialized(srcRange));
if (IsCompleteSubresourceCopiedTo(dstTexture, copySize, dst.mipLevel)) {
dstTexture->SetIsSubresourceContentInitialized(true, dstRange);
} else {
DAWN_TRY(dstTexture->EnsureSubresourceContentInitialized(dstRange));
}
CopyImageSubData(gl, src.aspect, srcTexture->GetHandle(), srcTexture->GetGLTarget(),
src.mipLevel, src.origin, dstTexture->GetHandle(),
dstTexture->GetGLTarget(), dst.mipLevel, dst.origin, copySize);
ToBackend(dst.texture)->Touch();
break;
}
case Command::ClearBuffer: {
ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>();
if (cmd->size == 0) {
// Skip no-op fills.
break;
}
Buffer* dstBuffer = ToBackend(cmd->buffer.Get());
bool clearedToZero =
dstBuffer->EnsureDataInitializedAsDestination(cmd->offset, cmd->size);
if (!clearedToZero) {
const std::vector<uint8_t> clearValues(cmd->size, 0u);
gl.BindBuffer(GL_ARRAY_BUFFER, dstBuffer->GetHandle());
gl.BufferSubData(GL_ARRAY_BUFFER, cmd->offset, cmd->size, clearValues.data());
}
dstBuffer->TrackUsage();
break;
}
case Command::ResolveQuerySet: {
// TODO(crbug.com/dawn/434): Resolve non-precise occlusion query.
SkipCommand(&mCommands, type);
break;
}
case Command::WriteTimestamp: {
return DAWN_UNIMPLEMENTED_ERROR("WriteTimestamp unimplemented");
}
case Command::InsertDebugMarker:
case Command::PopDebugGroup:
case Command::PushDebugGroup: {
// Due to lack of linux driver support for GL_EXT_debug_marker
// extension these functions are skipped.
SkipCommand(&mCommands, type);
break;
}
case Command::WriteBuffer: {
WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>();
uint64_t offset = write->offset;
uint64_t size = write->size;
if (size == 0) {
continue;
}
Buffer* dstBuffer = ToBackend(write->buffer.Get());
uint8_t* data = mCommands.NextData<uint8_t>(size);
dstBuffer->EnsureDataInitializedAsDestination(offset, size);
gl.BindBuffer(GL_ARRAY_BUFFER, dstBuffer->GetHandle());
gl.BufferSubData(GL_ARRAY_BUFFER, offset, size, data);
dstBuffer->TrackUsage();
break;
}
default:
UNREACHABLE();
}
}
return {};
}
MaybeError CommandBuffer::ExecuteComputePass() {
const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL();
ComputePipeline* lastPipeline = nullptr;
BindGroupTracker bindGroupTracker = {};
Command type;
while (mCommands.NextCommandId(&type)) {
switch (type) {
case Command::EndComputePass: {
mCommands.NextCommand<EndComputePassCmd>();
return {};
}
case Command::Dispatch: {
DispatchCmd* dispatch = mCommands.NextCommand<DispatchCmd>();
bindGroupTracker.Apply(gl);
gl.DispatchCompute(dispatch->x, dispatch->y, dispatch->z);
gl.MemoryBarrier(GL_ALL_BARRIER_BITS);
break;
}
case Command::DispatchIndirect: {
DispatchIndirectCmd* dispatch = mCommands.NextCommand<DispatchIndirectCmd>();
bindGroupTracker.Apply(gl);
uint64_t indirectBufferOffset = dispatch->indirectOffset;
Buffer* indirectBuffer = ToBackend(dispatch->indirectBuffer.Get());
gl.BindBuffer(GL_DISPATCH_INDIRECT_BUFFER, indirectBuffer->GetHandle());
gl.DispatchComputeIndirect(static_cast<GLintptr>(indirectBufferOffset));
gl.MemoryBarrier(GL_ALL_BARRIER_BITS);
indirectBuffer->TrackUsage();
break;
}
case Command::SetComputePipeline: {
SetComputePipelineCmd* cmd = mCommands.NextCommand<SetComputePipelineCmd>();
lastPipeline = ToBackend(cmd->pipeline).Get();
lastPipeline->ApplyNow();
bindGroupTracker.OnSetPipeline(lastPipeline);
break;
}
case Command::SetBindGroup: {
SetBindGroupCmd* cmd = mCommands.NextCommand<SetBindGroupCmd>();
uint32_t* dynamicOffsets = nullptr;
if (cmd->dynamicOffsetCount > 0) {
dynamicOffsets = mCommands.NextData<uint32_t>(cmd->dynamicOffsetCount);
}
bindGroupTracker.OnSetBindGroup(cmd->index, cmd->group.Get(),
cmd->dynamicOffsetCount, dynamicOffsets);
break;
}
case Command::InsertDebugMarker:
case Command::PopDebugGroup:
case Command::PushDebugGroup: {
// Due to lack of linux driver support for GL_EXT_debug_marker
// extension these functions are skipped.
SkipCommand(&mCommands, type);
break;
}
case Command::WriteTimestamp: {
return DAWN_UNIMPLEMENTED_ERROR("WriteTimestamp unimplemented");
}
default:
UNREACHABLE();
}
}
// EndComputePass should have been called
UNREACHABLE();
}
MaybeError CommandBuffer::ExecuteRenderPass(BeginRenderPassCmd* renderPass) {
const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL();
GLuint fbo = 0;
// Create the framebuffer used for this render pass and calls the correct glDrawBuffers
{
// TODO(kainino@chromium.org): This is added to possibly work around an issue seen on
// Windows/Intel. It should break any feedback loop before the clears, even if there
// shouldn't be any negative effects from this. Investigate whether it's actually
// needed.
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, 0);
// TODO(kainino@chromium.org): possible future optimization: create these framebuffers
// at Framebuffer build time (or maybe CommandBuffer build time) so they don't have to
// be created and destroyed at draw time.
gl.GenFramebuffers(1, &fbo);
gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
// Mapping from attachmentSlot to GL framebuffer attachment points. Defaults to zero
// (GL_NONE).
ityp::array<ColorAttachmentIndex, GLenum, kMaxColorAttachments> drawBuffers = {};
// Construct GL framebuffer
ColorAttachmentIndex attachmentCount(uint8_t(0));
for (ColorAttachmentIndex i :
IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
TextureView* textureView = ToBackend(renderPass->colorAttachments[i].view.Get());
GLenum glAttachment = GL_COLOR_ATTACHMENT0 + static_cast<uint8_t>(i);
// Attach color buffers.
textureView->BindToFramebuffer(GL_DRAW_FRAMEBUFFER, glAttachment);
drawBuffers[i] = glAttachment;
attachmentCount = i;
attachmentCount++;
}
gl.DrawBuffers(static_cast<uint8_t>(attachmentCount), drawBuffers.data());
if (renderPass->attachmentState->HasDepthStencilAttachment()) {
TextureView* textureView = ToBackend(renderPass->depthStencilAttachment.view.Get());
const Format& format = textureView->GetTexture()->GetFormat();
// Attach depth/stencil buffer.
GLenum glAttachment = 0;
if (format.aspects == (Aspect::Depth | Aspect::Stencil)) {
glAttachment = GL_DEPTH_STENCIL_ATTACHMENT;
} else if (format.aspects == Aspect::Depth) {
glAttachment = GL_DEPTH_ATTACHMENT;
} else if (format.aspects == Aspect::Stencil) {
glAttachment = GL_STENCIL_ATTACHMENT;
} else {
UNREACHABLE();
}
textureView->BindToFramebuffer(GL_DRAW_FRAMEBUFFER, glAttachment);
}
}
ASSERT(gl.CheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
// Set defaults for dynamic state before executing clears and commands.
PersistentPipelineState persistentPipelineState;
persistentPipelineState.SetDefaultState(gl);
gl.BlendColor(0, 0, 0, 0);
gl.Viewport(0, 0, renderPass->width, renderPass->height);
gl.DepthRangef(0.0, 1.0);
gl.Scissor(0, 0, renderPass->width, renderPass->height);
// Clear framebuffer attachments as needed
{
for (ColorAttachmentIndex index :
IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
uint8_t i = static_cast<uint8_t>(index);
auto* attachmentInfo = &renderPass->colorAttachments[index];
// Load op - color
if (attachmentInfo->loadOp == wgpu::LoadOp::Clear) {
gl.ColorMask(true, true, true, true);
TextureComponentType baseType =
attachmentInfo->view->GetFormat().GetAspectInfo(Aspect::Color).baseType;
switch (baseType) {
case TextureComponentType::Float: {
const std::array<float, 4> appliedClearColor =
ConvertToFloatColor(attachmentInfo->clearColor);
gl.ClearBufferfv(GL_COLOR, i, appliedClearColor.data());
break;
}
case TextureComponentType::Uint: {
const std::array<uint32_t, 4> appliedClearColor =
ConvertToUnsignedIntegerColor(attachmentInfo->clearColor);
gl.ClearBufferuiv(GL_COLOR, i, appliedClearColor.data());
break;
}
case TextureComponentType::Sint: {
const std::array<int32_t, 4> appliedClearColor =
ConvertToSignedIntegerColor(attachmentInfo->clearColor);
gl.ClearBufferiv(GL_COLOR, i, appliedClearColor.data());
break;
}
}
}
if (attachmentInfo->storeOp == wgpu::StoreOp::Discard) {
// TODO(natlee@microsoft.com): call glDiscard to do optimization
}
}
if (renderPass->attachmentState->HasDepthStencilAttachment()) {
auto* attachmentInfo = &renderPass->depthStencilAttachment;
const Format& attachmentFormat = attachmentInfo->view->GetTexture()->GetFormat();
// Load op - depth/stencil
bool doDepthClear =
attachmentFormat.HasDepth() && (attachmentInfo->depthLoadOp == wgpu::LoadOp::Clear);
bool doStencilClear = attachmentFormat.HasStencil() &&
(attachmentInfo->stencilLoadOp == wgpu::LoadOp::Clear);
if (doDepthClear) {
gl.DepthMask(GL_TRUE);
}
if (doStencilClear) {
gl.StencilMask(GetStencilMaskFromStencilFormat(attachmentFormat.format));
}
if (doDepthClear && doStencilClear) {
gl.ClearBufferfi(GL_DEPTH_STENCIL, 0, attachmentInfo->clearDepth,
attachmentInfo->clearStencil);
} else if (doDepthClear) {
gl.ClearBufferfv(GL_DEPTH, 0, &attachmentInfo->clearDepth);
} else if (doStencilClear) {
const GLint clearStencil = attachmentInfo->clearStencil;
gl.ClearBufferiv(GL_STENCIL, 0, &clearStencil);
}
}
}
RenderPipeline* lastPipeline = nullptr;
uint64_t indexBufferBaseOffset = 0;
GLenum indexBufferFormat;
uint32_t indexFormatSize;
VertexStateBufferBindingTracker vertexStateBufferBindingTracker;
BindGroupTracker bindGroupTracker = {};
auto DoRenderBundleCommand = [&](CommandIterator* iter, Command type) {
switch (type) {
case Command::Draw: {
DrawCmd* draw = iter->NextCommand<DrawCmd>();
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
if (gl.DrawArraysInstancedBaseInstanceANGLE) {
gl.DrawArraysInstancedBaseInstanceANGLE(
lastPipeline->GetGLPrimitiveTopology(), draw->firstVertex,
draw->vertexCount, draw->instanceCount, draw->firstInstance);
} else if (draw->firstInstance > 0) {
gl.DrawArraysInstancedBaseInstance(lastPipeline->GetGLPrimitiveTopology(),
draw->firstVertex, draw->vertexCount,
draw->instanceCount, draw->firstInstance);
} else {
// This branch is only needed on OpenGL < 4.2
gl.DrawArraysInstanced(lastPipeline->GetGLPrimitiveTopology(),
draw->firstVertex, draw->vertexCount,
draw->instanceCount);
}
break;
}
case Command::DrawIndexed: {
DrawIndexedCmd* draw = iter->NextCommand<DrawIndexedCmd>();
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
if (gl.DrawElementsInstancedBaseVertexBaseInstanceANGLE) {
gl.DrawElementsInstancedBaseVertexBaseInstanceANGLE(
lastPipeline->GetGLPrimitiveTopology(), draw->indexCount, indexBufferFormat,
reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
indexBufferBaseOffset),
draw->instanceCount, draw->baseVertex, draw->firstInstance);
} else if (draw->firstInstance > 0) {
gl.DrawElementsInstancedBaseVertexBaseInstance(
lastPipeline->GetGLPrimitiveTopology(), draw->indexCount, indexBufferFormat,
reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
indexBufferBaseOffset),
draw->instanceCount, draw->baseVertex, draw->firstInstance);
} else {
// This branch is only needed on OpenGL < 4.2; ES < 3.2
if (draw->baseVertex != 0) {
gl.DrawElementsInstancedBaseVertex(
lastPipeline->GetGLPrimitiveTopology(), draw->indexCount,
indexBufferFormat,
reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
indexBufferBaseOffset),
draw->instanceCount, draw->baseVertex);
} else {
// This branch is only needed on OpenGL < 3.2; ES < 3.2
gl.DrawElementsInstanced(
lastPipeline->GetGLPrimitiveTopology(), draw->indexCount,
indexBufferFormat,
reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
indexBufferBaseOffset),
draw->instanceCount);
}
}
break;
}
case Command::DrawIndirect: {
DrawIndirectCmd* draw = iter->NextCommand<DrawIndirectCmd>();
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
uint64_t indirectBufferOffset = draw->indirectOffset;
Buffer* indirectBuffer = ToBackend(draw->indirectBuffer.Get());
gl.BindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer->GetHandle());
gl.DrawArraysIndirect(
lastPipeline->GetGLPrimitiveTopology(),
reinterpret_cast<void*>(static_cast<intptr_t>(indirectBufferOffset)));
indirectBuffer->TrackUsage();
break;
}
case Command::DrawIndexedIndirect: {
DrawIndexedIndirectCmd* draw = iter->NextCommand<DrawIndexedIndirectCmd>();
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
Buffer* indirectBuffer = ToBackend(draw->indirectBuffer.Get());
ASSERT(indirectBuffer != nullptr);
gl.BindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer->GetHandle());
gl.DrawElementsIndirect(
lastPipeline->GetGLPrimitiveTopology(), indexBufferFormat,
reinterpret_cast<void*>(static_cast<intptr_t>(draw->indirectOffset)));
indirectBuffer->TrackUsage();
break;
}
case Command::InsertDebugMarker:
case Command::PopDebugGroup:
case Command::PushDebugGroup: {
// Due to lack of linux driver support for GL_EXT_debug_marker
// extension these functions are skipped.
SkipCommand(iter, type);
break;
}
case Command::SetRenderPipeline: {
SetRenderPipelineCmd* cmd = iter->NextCommand<SetRenderPipelineCmd>();
lastPipeline = ToBackend(cmd->pipeline).Get();
lastPipeline->ApplyNow(persistentPipelineState);
vertexStateBufferBindingTracker.OnSetPipeline(lastPipeline);
bindGroupTracker.OnSetPipeline(lastPipeline);
break;
}
case Command::SetBindGroup: {
SetBindGroupCmd* cmd = iter->NextCommand<SetBindGroupCmd>();
uint32_t* dynamicOffsets = nullptr;
if (cmd->dynamicOffsetCount > 0) {
dynamicOffsets = iter->NextData<uint32_t>(cmd->dynamicOffsetCount);
}
bindGroupTracker.OnSetBindGroup(cmd->index, cmd->group.Get(),
cmd->dynamicOffsetCount, dynamicOffsets);
break;
}
case Command::SetIndexBuffer: {
SetIndexBufferCmd* cmd = iter->NextCommand<SetIndexBufferCmd>();
indexBufferBaseOffset = cmd->offset;
indexBufferFormat = IndexFormatType(cmd->format);
indexFormatSize = IndexFormatSize(cmd->format);
vertexStateBufferBindingTracker.OnSetIndexBuffer(cmd->buffer.Get());
ToBackend(cmd->buffer)->TrackUsage();
break;
}
case Command::SetVertexBuffer: {
SetVertexBufferCmd* cmd = iter->NextCommand<SetVertexBufferCmd>();
vertexStateBufferBindingTracker.OnSetVertexBuffer(cmd->slot, cmd->buffer.Get(),
cmd->offset);
ToBackend(cmd->buffer)->TrackUsage();
break;
}
default:
UNREACHABLE();
break;
}
};
Command type;
while (mCommands.NextCommandId(&type)) {
switch (type) {
case Command::EndRenderPass: {
mCommands.NextCommand<EndRenderPassCmd>();
for (ColorAttachmentIndex i :
IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) {
TextureView* textureView =
ToBackend(renderPass->colorAttachments[i].view.Get());
ToBackend(textureView->GetTexture())->Touch();
}
if (renderPass->attachmentState->HasDepthStencilAttachment()) {
TextureView* textureView =
ToBackend(renderPass->depthStencilAttachment.view.Get());
ToBackend(textureView->GetTexture())->Touch();
}
if (renderPass->attachmentState->GetSampleCount() > 1) {
ResolveMultisampledRenderTargets(gl, renderPass);
}
gl.DeleteFramebuffers(1, &fbo);
return {};
}
case Command::SetStencilReference: {
SetStencilReferenceCmd* cmd = mCommands.NextCommand<SetStencilReferenceCmd>();
persistentPipelineState.SetStencilReference(gl, cmd->reference);
break;
}
case Command::SetViewport: {
SetViewportCmd* cmd = mCommands.NextCommand<SetViewportCmd>();
if (gl.IsAtLeastGL(4, 1)) {
gl.ViewportIndexedf(0, cmd->x, cmd->y, cmd->width, cmd->height);
} else {
// Floating-point viewport coords are unsupported on OpenGL ES, but
// truncation is ok because other APIs do not guarantee subpixel precision
// either.
gl.Viewport(static_cast<int>(cmd->x), static_cast<int>(cmd->y),
static_cast<int>(cmd->width), static_cast<int>(cmd->height));
}
gl.DepthRangef(cmd->minDepth, cmd->maxDepth);
break;
}
case Command::SetScissorRect: {
SetScissorRectCmd* cmd = mCommands.NextCommand<SetScissorRectCmd>();
gl.Scissor(cmd->x, cmd->y, cmd->width, cmd->height);
break;
}
case Command::SetBlendConstant: {
SetBlendConstantCmd* cmd = mCommands.NextCommand<SetBlendConstantCmd>();
const std::array<float, 4> blendColor = ConvertToFloatColor(cmd->color);
gl.BlendColor(blendColor[0], blendColor[1], blendColor[2], blendColor[3]);
break;
}
case Command::ExecuteBundles: {
ExecuteBundlesCmd* cmd = mCommands.NextCommand<ExecuteBundlesCmd>();
auto bundles = mCommands.NextData<Ref<RenderBundleBase>>(cmd->count);
for (uint32_t i = 0; i < cmd->count; ++i) {
CommandIterator* iter = bundles[i]->GetCommands();
iter->Reset();
while (iter->NextCommandId(&type)) {
DoRenderBundleCommand(iter, type);
}
}
break;
}
case Command::BeginOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("BeginOcclusionQuery unimplemented.");
}
case Command::EndOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("EndOcclusionQuery unimplemented.");
}
case Command::WriteTimestamp:
return DAWN_UNIMPLEMENTED_ERROR("WriteTimestamp unimplemented");
default: {
DoRenderBundleCommand(&mCommands, type);
break;
}
}
}
// EndRenderPass should have been called
UNREACHABLE();
}
void DoTexSubImage(const OpenGLFunctions& gl,
const TextureCopy& destination,
const void* data,
const TextureDataLayout& dataLayout,
const Extent3D& copySize) {
Texture* texture = ToBackend(destination.texture.Get());
const GLFormat& format = texture->GetGLFormat();
GLenum target = texture->GetGLTarget();
data = static_cast<const uint8_t*>(data) + dataLayout.offset;
gl.ActiveTexture(GL_TEXTURE0);
gl.BindTexture(target, texture->GetHandle());
const TexelBlockInfo& blockInfo = texture->GetFormat().GetAspectInfo(destination.aspect).block;
uint32_t x = destination.origin.x;
uint32_t y = destination.origin.y;
uint32_t z = destination.origin.z;
if (texture->GetFormat().isCompressed) {
size_t rowSize = copySize.width / blockInfo.width * blockInfo.byteSize;
Extent3D virtSize = texture->GetMipLevelSingleSubresourceVirtualSize(destination.mipLevel);
uint32_t width = std::min(copySize.width, virtSize.width - x);
// In GLES glPixelStorei() doesn't affect CompressedTexSubImage*D() and
// GL_UNPACK_COMPRESSED_BLOCK_* isn't defined, so we have to workaround
// this limitation by copying the compressed texture data once per row.
// See OpenGL ES 3.2 SPEC Chapter 8.4.1, "Pixel Storage Modes and Pixel
// Buffer Objects" for more details. For Desktop GL, we use row-by-row
// copies only for uploads where bytesPerRow is not a multiple of byteSize.
if (dataLayout.bytesPerRow % blockInfo.byteSize == 0 && gl.GetVersion().IsDesktop()) {
size_t imageSize =
rowSize * (copySize.height / blockInfo.height) * copySize.depthOrArrayLayers;
uint32_t height = std::min(copySize.height, virtSize.height - y);
gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_SIZE, blockInfo.byteSize);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH, blockInfo.width);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, blockInfo.height);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 1);
if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
gl.CompressedTexSubImage2D(target, destination.mipLevel, x, y, width, height,
format.internalFormat, imageSize, data);
} else {
gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, dataLayout.rowsPerImage * blockInfo.height);
gl.CompressedTexSubImage3D(target, destination.mipLevel, x, y, z, width, height,
copySize.depthOrArrayLayers, format.internalFormat,
imageSize, data);
gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
}
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_SIZE, 0);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH, 0);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, 0);
gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 0);
} else {
if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
const uint8_t* d = static_cast<const uint8_t*>(data);
for (; y < destination.origin.y + copySize.height; y += blockInfo.height) {
uint32_t height = std::min(blockInfo.height, virtSize.height - y);
gl.CompressedTexSubImage2D(target, destination.mipLevel, x, y, width, height,
format.internalFormat, rowSize, d);
d += dataLayout.bytesPerRow;
}
} else {
const uint8_t* slice = static_cast<const uint8_t*>(data);
for (; z < destination.origin.z + copySize.depthOrArrayLayers; ++z) {
const uint8_t* d = slice;
for (y = destination.origin.y; y < destination.origin.y + copySize.height;
y += blockInfo.height) {
uint32_t height = std::min(blockInfo.height, virtSize.height - y);
gl.CompressedTexSubImage3D(target, destination.mipLevel, x, y, z, width,
height, 1, format.internalFormat, rowSize, d);
d += dataLayout.bytesPerRow;
}
slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
}
}
}
} else {
uint32_t width = copySize.width;
uint32_t height = copySize.height;
if (dataLayout.bytesPerRow % blockInfo.byteSize == 0) {
gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width);
if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
gl.TexSubImage2D(target, destination.mipLevel, x, y, width, height, format.format,
format.type, data);
} else {
gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, dataLayout.rowsPerImage * blockInfo.height);
gl.TexSubImage3D(target, destination.mipLevel, x, y, z, width, height,
copySize.depthOrArrayLayers, format.format, format.type, data);
gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
}
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
} else {
if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
const uint8_t* d = static_cast<const uint8_t*>(data);
for (; y < destination.origin.y + height; ++y) {
gl.TexSubImage2D(target, destination.mipLevel, x, y, width, 1, format.format,
format.type, d);
d += dataLayout.bytesPerRow;
}
} else {
const uint8_t* slice = static_cast<const uint8_t*>(data);
for (; z < destination.origin.z + copySize.depthOrArrayLayers; ++z) {
const uint8_t* d = slice;
for (y = destination.origin.y; y < destination.origin.y + height; ++y) {
gl.TexSubImage3D(target, destination.mipLevel, x, y, z, width, 1, 1,
format.format, format.type, d);
d += dataLayout.bytesPerRow;
}
slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
}
}
}
}
}
} // namespace dawn::native::opengl