// 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/TextureGL.h" #include #include #include "dawn/common/Assert.h" #include "dawn/common/Constants.h" #include "dawn/common/Math.h" #include "dawn/native/EnumMaskIterator.h" #include "dawn/native/opengl/BufferGL.h" #include "dawn/native/opengl/CommandBufferGL.h" #include "dawn/native/opengl/DeviceGL.h" #include "dawn/native/opengl/UtilsGL.h" namespace dawn::native::opengl { namespace { GLenum TargetForTexture(const TextureDescriptor* descriptor) { switch (descriptor->dimension) { case wgpu::TextureDimension::e1D: case wgpu::TextureDimension::e2D: if (descriptor->size.depthOrArrayLayers > 1) { ASSERT(descriptor->sampleCount == 1); return GL_TEXTURE_2D_ARRAY; } else { if (descriptor->sampleCount > 1) { return GL_TEXTURE_2D_MULTISAMPLE; } else { return GL_TEXTURE_2D; } } case wgpu::TextureDimension::e3D: ASSERT(descriptor->sampleCount == 1); return GL_TEXTURE_3D; } UNREACHABLE(); } GLenum TargetForTextureViewDimension(wgpu::TextureViewDimension dimension, uint32_t arrayLayerCount, uint32_t sampleCount) { switch (dimension) { case wgpu::TextureViewDimension::e1D: case wgpu::TextureViewDimension::e2D: return (sampleCount > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; case wgpu::TextureViewDimension::e2DArray: if (sampleCount > 1) { ASSERT(arrayLayerCount == 1); return GL_TEXTURE_2D_MULTISAMPLE; } ASSERT(sampleCount == 1); return GL_TEXTURE_2D_ARRAY; case wgpu::TextureViewDimension::Cube: ASSERT(sampleCount == 1); ASSERT(arrayLayerCount == 6); return GL_TEXTURE_CUBE_MAP; case wgpu::TextureViewDimension::CubeArray: ASSERT(sampleCount == 1); ASSERT(arrayLayerCount % 6 == 0); return GL_TEXTURE_CUBE_MAP_ARRAY; case wgpu::TextureViewDimension::e3D: return GL_TEXTURE_3D; case wgpu::TextureViewDimension::Undefined: break; } UNREACHABLE(); } bool RequiresCreatingNewTextureView(const TextureBase* texture, const TextureViewDescriptor* textureViewDescriptor) { constexpr wgpu::TextureUsage kShaderUsageNeedsView = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding; constexpr wgpu::TextureUsage kUsageNeedsView = kShaderUsageNeedsView | wgpu::TextureUsage::RenderAttachment; if ((texture->GetInternalUsage() & kUsageNeedsView) == 0) { return false; } if (texture->GetFormat().format != textureViewDescriptor->format && !texture->GetFormat().HasDepthOrStencil()) { // Color format reinterpretation required. Note: Depth/stencil formats don't support // reinterpretation. return true; } // Reinterpretation not required. Now, we only need a new view if the view dimension or // set of subresources for the shader is different from the base texture. if ((texture->GetInternalUsage() & kShaderUsageNeedsView) == 0) { return false; } if (texture->GetArrayLayers() != textureViewDescriptor->arrayLayerCount || (texture->GetArrayLayers() == 1 && texture->GetDimension() == wgpu::TextureDimension::e2D && textureViewDescriptor->dimension == wgpu::TextureViewDimension::e2DArray)) { // If the view has a different number of array layers, we need a new view. // And, if the original texture is a 2D texture with one array layer, we need a new // view to view it as a 2D array texture. return true; } if (texture->GetNumMipLevels() != textureViewDescriptor->mipLevelCount) { return true; } if (ToBackend(texture)->GetGLFormat().format == GL_DEPTH_STENCIL && (texture->GetUsage() & wgpu::TextureUsage::TextureBinding) != 0 && textureViewDescriptor->aspect == wgpu::TextureAspect::StencilOnly) { // We need a separate view for one of the depth or stencil planes // because each glTextureView needs it's own handle to set // GL_DEPTH_STENCIL_TEXTURE_MODE. Choose the stencil aspect for the // extra handle since it is likely sampled less often. return true; } switch (textureViewDescriptor->dimension) { case wgpu::TextureViewDimension::Cube: case wgpu::TextureViewDimension::CubeArray: return true; default: break; } return false; } void AllocateTexture(const OpenGLFunctions& gl, GLenum target, GLsizei samples, GLuint levels, GLenum internalFormat, const Extent3D& size) { // glTextureView() requires the value of GL_TEXTURE_IMMUTABLE_FORMAT for origtexture to // be GL_TRUE, so the storage of the texture must be allocated with glTexStorage*D. // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTextureView.xhtml switch (target) { case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_3D: gl.TexStorage3D(target, levels, internalFormat, size.width, size.height, size.depthOrArrayLayers); break; case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: gl.TexStorage2D(target, levels, internalFormat, size.width, size.height); break; case GL_TEXTURE_2D_MULTISAMPLE: gl.TexStorage2DMultisample(target, samples, internalFormat, size.width, size.height, true); break; default: UNREACHABLE(); } } } // namespace // Texture // static ResultOrError> Texture::Create(Device* device, const TextureDescriptor* descriptor) { Ref texture = AcquireRef(new Texture(device, descriptor)); if (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) { DAWN_TRY( texture->ClearTexture(texture->GetAllSubresources(), TextureBase::ClearValue::NonZero)); } return std::move(texture); } Texture::Texture(Device* device, const TextureDescriptor* descriptor) : Texture(device, descriptor, 0, TextureState::OwnedInternal) { const OpenGLFunctions& gl = device->GetGL(); gl.GenTextures(1, &mHandle); uint32_t levels = GetNumMipLevels(); const GLFormat& glFormat = GetGLFormat(); gl.BindTexture(mTarget, mHandle); AllocateTexture(gl, mTarget, GetSampleCount(), levels, glFormat.internalFormat, GetSize()); // The texture is not complete if it uses mipmapping and not all levels up to // MAX_LEVEL have been defined. gl.TexParameteri(mTarget, GL_TEXTURE_MAX_LEVEL, levels - 1); } void Texture::Touch() { mGenID++; } uint32_t Texture::GetGenID() const { return mGenID; } Texture::Texture(Device* device, const TextureDescriptor* descriptor, GLuint handle, TextureState state) : TextureBase(device, descriptor, state), mHandle(handle) { mTarget = TargetForTexture(descriptor); } Texture::~Texture() {} void Texture::DestroyImpl() { TextureBase::DestroyImpl(); if (GetTextureState() == TextureState::OwnedInternal) { const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL(); gl.DeleteTextures(1, &mHandle); mHandle = 0; } } GLuint Texture::GetHandle() const { return mHandle; } GLenum Texture::GetGLTarget() const { return mTarget; } const GLFormat& Texture::GetGLFormat() const { return ToBackend(GetDevice())->GetGLFormat(GetFormat()); } MaybeError Texture::ClearTexture(const SubresourceRange& range, TextureBase::ClearValue clearValue) { // TODO(crbug.com/dawn/850): initialize the textures with compressed formats. if (GetFormat().isCompressed) { return {}; } Device* device = ToBackend(GetDevice()); const OpenGLFunctions& gl = device->GetGL(); uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1; float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f; if (GetFormat().isRenderable) { if (range.aspects & (Aspect::Depth | Aspect::Stencil)) { GLfloat depth = fClearColor; GLint stencil = clearColor; if (range.aspects & Aspect::Depth) { gl.DepthMask(GL_TRUE); } if (range.aspects & Aspect::Stencil) { gl.StencilMask(GetStencilMaskFromStencilFormat(GetFormat().format)); } auto DoClear = [&](Aspect aspects) { if (aspects == (Aspect::Depth | Aspect::Stencil)) { gl.ClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil); } else if (aspects == Aspect::Depth) { gl.ClearBufferfv(GL_DEPTH, 0, &depth); } else if (aspects == Aspect::Stencil) { gl.ClearBufferiv(GL_STENCIL, 0, &stencil); } else { UNREACHABLE(); } }; GLuint framebuffer = 0; gl.GenFramebuffers(1, &framebuffer); gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); gl.Disable(GL_SCISSOR_TEST); GLenum attachment; if (range.aspects == (Aspect::Depth | Aspect::Stencil)) { attachment = GL_DEPTH_STENCIL_ATTACHMENT; } else if (range.aspects == Aspect::Depth) { attachment = GL_DEPTH_ATTACHMENT; } else if (range.aspects == Aspect::Stencil) { attachment = GL_STENCIL_ATTACHMENT; } else { UNREACHABLE(); } for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount; ++level) { switch (GetDimension()) { case wgpu::TextureDimension::e1D: case wgpu::TextureDimension::e2D: if (GetArrayLayers() == 1) { Aspect aspectsToClear = Aspect::None; for (Aspect aspect : IterateEnumMask(range.aspects)) { if (clearValue == TextureBase::ClearValue::Zero && IsSubresourceContentInitialized( SubresourceRange::SingleMipAndLayer(level, 0, aspect))) { // Skip lazy clears if already initialized. continue; } aspectsToClear |= aspect; } if (aspectsToClear == Aspect::None) { continue; } gl.FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GetGLTarget(), GetHandle(), static_cast(level)); DoClear(aspectsToClear); } else { for (uint32_t layer = range.baseArrayLayer; layer < range.baseArrayLayer + range.layerCount; ++layer) { Aspect aspectsToClear = Aspect::None; for (Aspect aspect : IterateEnumMask(range.aspects)) { if (clearValue == TextureBase::ClearValue::Zero && IsSubresourceContentInitialized( SubresourceRange::SingleMipAndLayer(level, layer, aspect))) { // Skip lazy clears if already initialized. continue; } aspectsToClear |= aspect; } if (aspectsToClear == Aspect::None) { continue; } gl.FramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, attachment, GetHandle(), static_cast(level), static_cast(layer)); DoClear(aspectsToClear); } } break; case wgpu::TextureDimension::e3D: UNREACHABLE(); } } gl.Enable(GL_SCISSOR_TEST); gl.DeleteFramebuffers(1, &framebuffer); } else { ASSERT(range.aspects == Aspect::Color); // For gl.ClearBufferiv/uiv calls constexpr std::array kClearColorDataUint0 = {0u, 0u, 0u, 0u}; constexpr std::array kClearColorDataUint1 = {1u, 1u, 1u, 1u}; std::array clearColorData; clearColorData.fill((clearValue == TextureBase::ClearValue::Zero) ? 0u : 1u); // For gl.ClearBufferfv calls constexpr std::array kClearColorDataFloat0 = {0.f, 0.f, 0.f, 0.f}; constexpr std::array kClearColorDataFloat1 = {1.f, 1.f, 1.f, 1.f}; std::array fClearColorData; fClearColorData.fill((clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f); static constexpr uint32_t MAX_TEXEL_SIZE = 16; const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(Aspect::Color).block; ASSERT(blockInfo.byteSize <= MAX_TEXEL_SIZE); // For gl.ClearTexSubImage calls constexpr std::array kClearColorDataBytes0 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; constexpr std::array kClearColorDataBytes255 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; wgpu::TextureComponentType baseType = GetFormat().GetAspectInfo(Aspect::Color).baseType; const GLFormat& glFormat = GetGLFormat(); for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount; ++level) { Extent3D mipSize = GetMipLevelSingleSubresourcePhysicalSize(level); for (uint32_t layer = range.baseArrayLayer; layer < range.baseArrayLayer + range.layerCount; ++layer) { if (clearValue == TextureBase::ClearValue::Zero && IsSubresourceContentInitialized( SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) { // Skip lazy clears if already initialized. continue; } if (gl.IsAtLeastGL(4, 4)) { gl.ClearTexSubImage(mHandle, static_cast(level), 0, 0, static_cast(layer), mipSize.width, mipSize.height, mipSize.depthOrArrayLayers, glFormat.format, glFormat.type, clearValue == TextureBase::ClearValue::Zero ? kClearColorDataBytes0.data() : kClearColorDataBytes255.data()); continue; } GLuint framebuffer = 0; gl.GenFramebuffers(1, &framebuffer); gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); GLenum attachment = GL_COLOR_ATTACHMENT0; gl.DrawBuffers(1, &attachment); gl.Disable(GL_SCISSOR_TEST); gl.ColorMask(true, true, true, true); auto DoClear = [&]() { switch (baseType) { case wgpu::TextureComponentType::Float: { gl.ClearBufferfv(GL_COLOR, 0, clearValue == TextureBase::ClearValue::Zero ? kClearColorDataFloat0.data() : kClearColorDataFloat1.data()); break; } case wgpu::TextureComponentType::Uint: { gl.ClearBufferuiv(GL_COLOR, 0, clearValue == TextureBase::ClearValue::Zero ? kClearColorDataUint0.data() : kClearColorDataUint1.data()); break; } case wgpu::TextureComponentType::Sint: { gl.ClearBufferiv(GL_COLOR, 0, reinterpret_cast( clearValue == TextureBase::ClearValue::Zero ? kClearColorDataUint0.data() : kClearColorDataUint1.data())); break; } case wgpu::TextureComponentType::DepthComparison: UNREACHABLE(); } }; if (GetArrayLayers() == 1) { switch (GetDimension()) { case wgpu::TextureDimension::e1D: case wgpu::TextureDimension::e2D: gl.FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GetGLTarget(), GetHandle(), level); DoClear(); break; case wgpu::TextureDimension::e3D: uint32_t depth = GetMipLevelSingleSubresourceVirtualSize(level) .depthOrArrayLayers; for (GLint z = 0; z < static_cast(depth); ++z) { gl.FramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, attachment, GetHandle(), level, z); DoClear(); } break; } } else { ASSERT(GetDimension() == wgpu::TextureDimension::e2D); gl.FramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, attachment, GetHandle(), level, layer); DoClear(); } gl.Enable(GL_SCISSOR_TEST); gl.DeleteFramebuffers(1, &framebuffer); gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } } } } else { ASSERT(range.aspects == Aspect::Color); // create temp buffer with clear color to copy to the texture image const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(Aspect::Color).block; ASSERT(kTextureBytesPerRowAlignment % blockInfo.byteSize == 0); Extent3D largestMipSize = GetMipLevelSingleSubresourcePhysicalSize(range.baseMipLevel); uint32_t bytesPerRow = Align((largestMipSize.width / blockInfo.width) * blockInfo.byteSize, 4); // Make sure that we are not rounding ASSERT(bytesPerRow % blockInfo.byteSize == 0); ASSERT(largestMipSize.height % blockInfo.height == 0); uint64_t bufferSize64 = static_cast(bytesPerRow) * (largestMipSize.height / blockInfo.height) * largestMipSize.depthOrArrayLayers; if (bufferSize64 > std::numeric_limits::max()) { return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate buffer."); } size_t bufferSize = static_cast(bufferSize64); dawn::native::BufferDescriptor descriptor = {}; descriptor.mappedAtCreation = true; descriptor.usage = wgpu::BufferUsage::CopySrc; descriptor.size = bufferSize; // We don't count the lazy clear of srcBuffer because it is an internal buffer. // TODO(natlee@microsoft.com): use Dynamic Uploader here for temp buffer Ref srcBuffer; DAWN_TRY_ASSIGN(srcBuffer, Buffer::CreateInternalBuffer(device, &descriptor, false)); // Fill the buffer with clear color memset(srcBuffer->GetMappedRange(0, bufferSize), clearColor, bufferSize); DAWN_TRY(srcBuffer->Unmap()); gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, srcBuffer->GetHandle()); for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount; ++level) { TextureCopy textureCopy; textureCopy.texture = this; textureCopy.mipLevel = level; textureCopy.origin = {}; textureCopy.aspect = Aspect::Color; TextureDataLayout dataLayout; dataLayout.offset = 0; dataLayout.bytesPerRow = bytesPerRow; dataLayout.rowsPerImage = largestMipSize.height; Extent3D mipSize = GetMipLevelSingleSubresourcePhysicalSize(level); for (uint32_t layer = range.baseArrayLayer; layer < range.baseArrayLayer + range.layerCount; ++layer) { if (clearValue == TextureBase::ClearValue::Zero && IsSubresourceContentInitialized( SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) { // Skip lazy clears if already initialized. continue; } textureCopy.origin.z = layer; DoTexSubImage(gl, textureCopy, 0, dataLayout, mipSize); } } gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } if (clearValue == TextureBase::ClearValue::Zero) { SetIsSubresourceContentInitialized(true, range); device->IncrementLazyClearCountForTesting(); } Touch(); return {}; } MaybeError Texture::EnsureSubresourceContentInitialized(const SubresourceRange& range) { if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) { return {}; } if (!IsSubresourceContentInitialized(range)) { DAWN_TRY(ClearTexture(range, TextureBase::ClearValue::Zero)); } return {}; } // TextureView TextureView::TextureView(TextureBase* texture, const TextureViewDescriptor* descriptor) : TextureViewBase(texture, descriptor), mOwnsHandle(false) { mTarget = TargetForTextureViewDimension(descriptor->dimension, descriptor->arrayLayerCount, texture->GetSampleCount()); // Texture could be destroyed by the time we make a view. if (GetTexture()->GetTextureState() == Texture::TextureState::Destroyed) { return; } if (!RequiresCreatingNewTextureView(texture, descriptor)) { mHandle = ToBackend(texture)->GetHandle(); } else { const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL(); if (gl.IsAtLeastGL(4, 3)) { gl.GenTextures(1, &mHandle); const Texture* textureGL = ToBackend(texture); gl.TextureView(mHandle, mTarget, textureGL->GetHandle(), GetInternalFormat(), descriptor->baseMipLevel, descriptor->mipLevelCount, descriptor->baseArrayLayer, descriptor->arrayLayerCount); mOwnsHandle = true; } else { // Simulate glTextureView() with texture-to-texture copies. mUseCopy = true; mHandle = 0; } } } TextureView::~TextureView() {} void TextureView::DestroyImpl() { TextureViewBase::DestroyImpl(); if (mOwnsHandle) { const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL(); gl.DeleteTextures(1, &mHandle); } } GLuint TextureView::GetHandle() const { ASSERT(mHandle != 0); return mHandle; } GLenum TextureView::GetGLTarget() const { return mTarget; } void TextureView::BindToFramebuffer(GLenum target, GLenum attachment) { const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL(); // Use the base texture where possible to minimize the amount of copying required on GLES. bool useOwnView = GetFormat().format != GetTexture()->GetFormat().format && !GetTexture()->GetFormat().HasDepthOrStencil(); GLuint handle, textarget, mipLevel, arrayLayer; if (useOwnView) { // Use our own texture handle and target which points to a subset of the texture's // subresources. handle = GetHandle(); textarget = GetGLTarget(); mipLevel = 0; arrayLayer = 0; } else { // Use the texture's handle and target, with the view's base mip level and base array handle = ToBackend(GetTexture())->GetHandle(); textarget = ToBackend(GetTexture())->GetGLTarget(); mipLevel = GetBaseMipLevel(); arrayLayer = GetBaseArrayLayer(); } ASSERT(handle != 0); if (textarget == GL_TEXTURE_2D_ARRAY || textarget == GL_TEXTURE_3D) { gl.FramebufferTextureLayer(target, attachment, handle, mipLevel, arrayLayer); } else { gl.FramebufferTexture2D(target, attachment, textarget, handle, mipLevel); } } void TextureView::CopyIfNeeded() { if (!mUseCopy) { return; } const Texture* texture = ToBackend(GetTexture()); if (mGenID == texture->GetGenID()) { return; } Device* device = ToBackend(GetDevice()); const OpenGLFunctions& gl = device->GetGL(); uint32_t srcLevel = GetBaseMipLevel(); uint32_t numLevels = GetLevelCount(); uint32_t width = texture->GetWidth() >> srcLevel; uint32_t height = texture->GetHeight() >> srcLevel; Extent3D size{width, height, GetLayerCount()}; if (mHandle == 0) { gl.GenTextures(1, &mHandle); gl.BindTexture(mTarget, mHandle); AllocateTexture(gl, mTarget, texture->GetSampleCount(), numLevels, GetInternalFormat(), size); mOwnsHandle = true; } Origin3D src{0, 0, GetBaseArrayLayer()}; Origin3D dst{0, 0, 0}; for (GLuint level = 0; level < numLevels; ++level) { CopyImageSubData(gl, GetAspects(), texture->GetHandle(), texture->GetGLTarget(), srcLevel + level, src, mHandle, mTarget, level, dst, size); } mGenID = texture->GetGenID(); } GLenum TextureView::GetInternalFormat() const { // Depth/stencil don't support reinterpretation, and the aspect is specified at // bind time. In that case, we use the base texture format. const Format& format = GetFormat().HasDepthOrStencil() ? GetTexture()->GetFormat() : GetFormat(); const GLFormat& glFormat = ToBackend(GetDevice())->GetGLFormat(format); return glFormat.internalFormat; } } // namespace dawn::native::opengl