From d2cb53e39dc0955da688e45cc5b48477fb9ecd13 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 28 Jun 2024 10:29:45 -0700 Subject: [PATCH] add minimal support for Texture Views in the backends Texture Views are currently limited to the min/max lod, and it's not allowed to have several views of the same texture in a single shader. Only SAMPLEABLE textures can have a texture view. Additionally, setMinMaxLevels() is removed as well as all automatic min/max lods adjustments. This is all handled by filament itself from now on, using Texture Views. --- .../include/private/backend/DriverAPI.inc | 10 +- filament/backend/src/metal/MetalDriver.mm | 24 +- filament/backend/src/metal/MetalHandles.h | 28 +- filament/backend/src/metal/MetalHandles.mm | 56 +--- filament/backend/src/noop/NoopDriver.cpp | 3 - .../backend/src/opengl/GLDescriptorSet.cpp | 57 ++++- filament/backend/src/opengl/GLDescriptorSet.h | 21 +- filament/backend/src/opengl/GLTexture.h | 10 +- filament/backend/src/opengl/OpenGLDriver.cpp | 188 ++++++-------- filament/backend/src/opengl/OpenGLDriver.h | 2 - filament/backend/src/opengl/OpenGLProgram.cpp | 4 +- filament/backend/src/vulkan/VulkanDriver.cpp | 41 +-- filament/backend/src/vulkan/VulkanHandles.cpp | 5 +- filament/backend/src/vulkan/VulkanHandles.h | 4 +- .../backend/src/vulkan/VulkanSwapChain.cpp | 12 +- filament/backend/src/vulkan/VulkanSwapChain.h | 5 +- filament/backend/src/vulkan/VulkanTexture.cpp | 216 ++++++++++------ filament/backend/src/vulkan/VulkanTexture.h | 242 ++++++++++-------- filament/backend/test/test_FeedbackLoops.cpp | 37 ++- filament/backend/test/test_MipLevels.cpp | 25 +- filament/docs/Vulkan.md.html | 2 +- filament/src/PostProcessManager.cpp | 67 ++--- filament/src/PostProcessManager.h | 2 +- filament/src/ShadowMapManager.cpp | 4 +- filament/src/details/IndirectLight.cpp | 6 +- filament/src/details/MaterialInstance.cpp | 29 ++- filament/src/details/MaterialInstance.h | 10 + filament/src/details/RenderTarget.cpp | 4 +- filament/src/details/Texture.cpp | 64 +++++ filament/src/details/Texture.h | 29 ++- filament/src/ds/ColorPassDescriptorSet.cpp | 5 +- 31 files changed, 722 insertions(+), 490 deletions(-) diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 9575b1b8e7..924a1f92ad 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -196,6 +196,11 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture, uint32_t, depth, backend::TextureUsage, usage) +DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureView, + backend::TextureHandle, texture, + uint8_t, baseLevel, + uint8_t, levelCount) + DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureSwizzled, backend::SamplerType, target, uint8_t, levels, @@ -374,11 +379,6 @@ DECL_DRIVER_API_N(updateSamplerGroup, backend::SamplerGroupHandle, ubh, backend::BufferDescriptor&&, data) -DECL_DRIVER_API_N(setMinMaxLevels, - backend::TextureHandle, th, - uint32_t, minLevel, - uint32_t, maxLevel) - DECL_DRIVER_API_N(update3DImage, backend::TextureHandle, th, uint32_t, level, diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index e97aebbcda..871ef482d9 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -438,6 +438,13 @@ void MetalDriver::createTextureSwizzledR(Handle th, SamplerType targe th.getId(), stringify(target), levels, samples, width, height, depth, stringify(usage)); } +void MetalDriver::createTextureViewR(Handle th, + Handle srch, uint8_t baseLevel, uint8_t levelCount) { + MetalTexture const* src = handle_cast(srch); + mContext->textures.insert(construct_handle(th, *mContext, + src, baseLevel, levelCount)); +} + void MetalDriver::importTextureR(Handle th, intptr_t i, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, @@ -501,7 +508,6 @@ void MetalDriver::createRenderTargetR(Handle rth, auto colorTexture = handle_cast(buffer.handle); FILAMENT_CHECK_PRECONDITION(colorTexture->getMtlTextureForWrite()) << "Color texture passed to render target has no texture allocation"; - colorTexture->extendLodRangeTo(buffer.level); colorAttachments[i] = { colorTexture, color[i].level, color[i].layer }; } @@ -512,7 +518,6 @@ void MetalDriver::createRenderTargetR(Handle rth, auto depthTexture = handle_cast(depth.handle); FILAMENT_CHECK_PRECONDITION(depthTexture->getMtlTextureForWrite()) << "Depth texture passed to render target has no texture allocation."; - depthTexture->extendLodRangeTo(depth.level); depthAttachment = { depthTexture, depth.level, depth.layer }; } @@ -523,7 +528,6 @@ void MetalDriver::createRenderTargetR(Handle rth, auto stencilTexture = handle_cast(stencil.handle); FILAMENT_CHECK_PRECONDITION(stencilTexture->getMtlTextureForWrite()) << "Stencil texture passed to render target has no texture allocation."; - stencilTexture->extendLodRangeTo(stencil.level); stencilAttachment = { stencilTexture, stencil.level, stencil.layer }; } @@ -643,6 +647,10 @@ Handle MetalDriver::createTextureSwizzledS() noexcept { return alloc_handle(); } +Handle MetalDriver::createTextureViewS() noexcept { + return alloc_handle(); +} + Handle MetalDriver::importTextureS() noexcept { return alloc_handle(); } @@ -1084,14 +1092,6 @@ void MetalDriver::setVertexBufferObject(Handle vbh, uint32_t ind vertexBuffer->buffers[index] = bufferObject->getBuffer(); } -void MetalDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { - auto tex = handle_cast(th); - tex->setLodRange(minLevel, maxLevel); - - DEBUG_LOG("setMinMaxLevels(th = %d, minLevel = %d, maxLevel = %d)\n", th.getId(), minLevel, - maxLevel); -} - void MetalDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, @@ -1663,8 +1663,6 @@ void MetalDriver::blit( mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blit/resolve"); - dstTexture->extendLodRangeTo(dstLevel); - DEBUG_LOG( "blit(dst = %d, srcLevel = %d, srcLayer = %d, dstOrigin = (%d, %d), src = %d, dstLevel " "= %d, dstLayer = %d, srcOrigin = (%d, %d), size = (%d, %d))\n", diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index f723a37aeb..25de9c935d 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -229,8 +229,11 @@ class MetalTexture : public HwTexture { public: MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, - TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) - noexcept; + TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) noexcept; + + // constructor for creating a texture view + MetalTexture(MetalContext& context, + MetalTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept; // Constructor for importing an id outside of Filament. MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, @@ -239,28 +242,17 @@ public: ~MetalTexture(); - // Returns an id suitable for reading in a shader, taking into account swizzle and - // LOD clamping. - id getMtlTextureForRead() noexcept; + // Returns an id suitable for reading in a shader, taking into account swizzle. + id getMtlTextureForRead() const noexcept; // Returns the id for attaching to a render pass. - id getMtlTextureForWrite() noexcept { + id getMtlTextureForWrite() const noexcept { return texture; } void loadImage(uint32_t level, MTLRegion region, PixelBufferDescriptor& p) noexcept; void generateMipmaps() noexcept; - // A texture starts out with none of its mip levels (also referred to as LODs) available for - // reading. 4 actions update the range of LODs available: - // - calling loadImage - // - calling generateMipmaps - // - using the texture as a render target attachment - // - calling setMinMaxLevels - // A texture's available mips are consistent throughout a render pass. - void setLodRange(uint16_t minLevel, uint16_t maxLevel); - void extendLodRangeTo(uint16_t level); - static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format); MetalContext& context; @@ -307,10 +299,6 @@ private: // Filament swizzling only affects texture reads, so this should not be used when the texture is // bound as a render target attachment. id swizzledTextureView = nil; - id lodTextureView = nil; - - uint16_t minLod = std::numeric_limits::max(); - uint16_t maxLod = 0; bool terminated = false; }; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index fc71c8a895..a99f64317d 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -615,19 +615,27 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le } } +MetalTexture::MetalTexture(MetalContext& context, + MetalTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept + : HwTexture(src->target, src->levels, src->samples, + src->width, src->height, src->depth, src->format, src->usage), + context(context), + externalImage(context) { + texture = createTextureViewWithLodRange( + src->getMtlTextureForRead(), baseLevel, baseLevel + levelCount - 1); +} + MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, id metalTexture) noexcept : HwTexture(target, levels, samples, width, height, depth, format, usage), context(context), externalImage(context) { texture = metalTexture; - setLodRange(0, levels - 1); } void MetalTexture::terminate() noexcept { texture = nil; swizzledTextureView = nil; - lodTextureView = nil; msaaSidecar = nil; externalImage.set(nullptr); terminated = true; @@ -637,29 +645,9 @@ MetalTexture::~MetalTexture() { externalImage.set(nullptr); } -id MetalTexture::getMtlTextureForRead() noexcept { - // TODO: get LODs to work correctly - id tex = swizzledTextureView ? swizzledTextureView : texture; - return tex; - if (lodTextureView) { - return lodTextureView; - } - // The texture's swizzle remains constant throughout its lifetime, however its LOD range can - // change. We'll cache the LOD view, and set lodTextureView to nil if minLod or maxLod is - // updated. - // TODO: ah, but we can't set it to nil because we need to keep it alive if it has been latched. - id t = swizzledTextureView ? swizzledTextureView : texture; - if (!t) { - return nil; - } - if (UTILS_UNLIKELY(minLod > maxLod)) { - // If the texture does not have any available LODs, provide a view of only level 0. - // Filament should prevent this from ever occurring. - lodTextureView = createTextureViewWithLodRange(t, 0, 0); - return lodTextureView; - } - lodTextureView = createTextureViewWithLodRange(t, minLod, maxLod); - return lodTextureView; +id MetalTexture::getMtlTextureForRead() const noexcept { + // TODO: get LODs to work correctly with swizzling + return swizzledTextureView ? swizzledTextureView : texture; } MTLPixelFormat MetalTexture::decidePixelFormat(MetalContext* context, TextureFormat format) { @@ -778,15 +766,12 @@ void MetalTexture::loadImage(uint32_t level, MTLRegion region, PixelBufferDescri assert_invariant(false); } } - - extendLodRangeTo(level); } void MetalTexture::generateMipmaps() noexcept { id blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder]; [blitEncoder generateMipmapsForTexture:texture]; [blitEncoder endEncoding]; - setLodRange(0, texture.mipmapLevelCount - 1); } void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice, @@ -910,21 +895,6 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit"); } -void MetalTexture::extendLodRangeTo(uint16_t level) { - assert_invariant(!isInRenderPass(&context)); - minLod = std::min(minLod, level); - maxLod = std::max(maxLod, level); - lodTextureView = nil; -} - -void MetalTexture::setLodRange(uint16_t min, uint16_t max) { - assert_invariant(!isInRenderPass(&context)); - assert_invariant(min <= max); - minLod = min; - maxLod = max; - lodTextureView = nil; -} - void MetalSamplerGroup::finalize() { assert_invariant(encoder); // TODO: we should be able to encode textures and samplers inside setFinalizedTexture and diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index ba7d6b92d5..e81244601a 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -250,9 +250,6 @@ void NoopDriver::setVertexBufferObject(Handle vbh, uint32_t inde Handle boh) { } -void NoopDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { -} - void NoopDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, diff --git a/filament/backend/src/opengl/GLDescriptorSet.cpp b/filament/backend/src/opengl/GLDescriptorSet.cpp index b8070b64b1..0667f8497e 100644 --- a/filament/backend/src/opengl/GLDescriptorSet.cpp +++ b/filament/backend/src/opengl/GLDescriptorSet.cpp @@ -20,6 +20,7 @@ #include "GLDescriptorSetLayout.h" #include "GLTexture.h" #include "GLUtils.h" +#include "OpenGLDriver.h" #include "OpenGLContext.h" #include "OpenGLProgram.h" @@ -168,9 +169,16 @@ void GLDescriptorSet::update(OpenGLContext& gl, arg.target = t ? t->gl.target : 0; arg.id = t ? t->gl.id : 0; - arg.anisotropy = float(1u << params.anisotropyLog2); if constexpr (std::is_same_v || std::is_same_v) { + if constexpr (std::is_same_v) { + arg.anisotropy = float(1u << params.anisotropyLog2); + } + if (t) { + arg.ref = t->ref; + arg.baseLevel = t->gl.baseLevel; + arg.maxLevel = t->gl.maxLevel; + } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 arg.sampler = gl.getSampler(params); #else @@ -186,8 +194,36 @@ void GLDescriptorSet::update(OpenGLContext& gl, }, descriptors[binding].desc); } -void GLDescriptorSet::bind(OpenGLContext& gl, OpenGLProgram const& p, descriptor_set_t set, - uint32_t const* offsets, bool offsetsOnly) const noexcept { +template +void GLDescriptorSet::updateTextureLod(OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept { + // The common case is that we don't have a ref handle (we only have one if + // the texture ever had a View on it. + GLTextureRef* const ref = handleAllocator.handle_cast(desc.ref); + if (UTILS_UNLIKELY((desc.baseLevel != ref->baseLevel + || desc.maxLevel != ref->maxLevel))) { + // If we have views, then it's still uncommon that we'll switch often + // handle the case where we reset to the original texture + GLint baseLevel = desc.baseLevel; + GLint maxLevel = desc.maxLevel; + if (baseLevel > maxLevel) { + baseLevel = 0; + maxLevel = 1000; // per OpenGL spec + } + // that is very unfortunate that we have to call activeTexture here + gl.activeTexture(unit); + glTexParameteri(desc.target, GL_TEXTURE_BASE_LEVEL, baseLevel); + glTexParameteri(desc.target, GL_TEXTURE_MAX_LEVEL, maxLevel); + ref->baseLevel = desc.baseLevel; + ref->maxLevel = desc.maxLevel; + } +} + +void GLDescriptorSet::bind( + OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, + OpenGLProgram const& p, + descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept { // TODO: check that offsets is sized correctly size_t dynamicOffsetIndex = 0; @@ -197,7 +233,8 @@ void GLDescriptorSet::bind(OpenGLContext& gl, OpenGLProgram const& p, descriptor } // loop only over the active indices for this program - activeDescriptorBindings.forEachSetBit([this, &gl, &p, set, offsets, &dynamicOffsetIndex] + activeDescriptorBindings.forEachSetBit( + [this,&gl, &handleAllocator, &p, set, offsets, &dynamicOffsetIndex] (size_t binding) { // This would fail here if we're trying to set a descriptor that doesn't exist in the @@ -205,7 +242,9 @@ void GLDescriptorSet::bind(OpenGLContext& gl, OpenGLProgram const& p, descriptor assert_invariant(binding < descriptors.size()); auto const& entry = descriptors[binding]; - std::visit([&gl, &p, &dynamicOffsetIndex, set, binding, offsets](auto&& arg) { + std::visit( + [&gl, &handleAllocator, &p, &dynamicOffsetIndex, set, binding, offsets] + (auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { GLuint const bindingPoint = p.getBufferBinding(set, binding); @@ -232,6 +271,9 @@ void GLDescriptorSet::bind(OpenGLContext& gl, OpenGLProgram const& p, descriptor if (arg.target) { gl.bindTexture(unit, arg.target, arg.id); gl.bindSampler(unit, arg.sampler); + if (UTILS_UNLIKELY(arg.ref)) { + updateTextureLod(gl, handleAllocator, unit, arg); + } } else { gl.unbindTextureUnit(unit); } @@ -240,11 +282,14 @@ void GLDescriptorSet::bind(OpenGLContext& gl, OpenGLProgram const& p, descriptor if (arg.target) { gl.bindTexture(unit, arg.target, arg.id); gl.bindSampler(unit, arg.sampler); + if (UTILS_UNLIKELY(arg.ref)) { + updateTextureLod(gl, handleAllocator, unit, arg); + } #if defined(GL_EXT_texture_filter_anisotropic) // Driver claims to support anisotropic filtering, but it fails when set on // the sampler, we have to set it on the texture instead. glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT, - std::min(gl.gets.max_anisotropy, arg.anisotropy)); + std::min(gl.gets.max_anisotropy, float(arg.anisotropy))); #endif } else { gl.unbindTextureUnit(unit); diff --git a/filament/backend/src/opengl/GLDescriptorSet.h b/filament/backend/src/opengl/GLDescriptorSet.h index 72d42fec33..db2548c916 100644 --- a/filament/backend/src/opengl/GLDescriptorSet.h +++ b/filament/backend/src/opengl/GLDescriptorSet.h @@ -29,6 +29,8 @@ #include #include +#include + #include #include @@ -38,6 +40,7 @@ namespace filament::backend { struct GLBufferObject; struct GLTexture; +struct GLTextureRef; struct GLDescriptorSetLayout; class OpenGLProgram; class OpenGLContext; @@ -59,7 +62,10 @@ struct GLDescriptorSet : public HwDescriptorSet { descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept; // conceptually bind the set to the command buffer - void bind(OpenGLContext& gl, OpenGLProgram const& p, + void bind( + OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, + OpenGLProgram const& p, descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept; uint32_t getDynamicBufferCount() const noexcept { @@ -103,14 +109,19 @@ private: GLenum target = 0; // 4 GLuint id = 0; // 4 GLuint sampler = 0; // 4 - float anisotropy = 1.0f; // 4 + Handle ref; // 4 + int8_t baseLevel = 0x7f; // 1 + int8_t maxLevel = -1; // 1 }; struct SamplerWithAnisotropyWorkaround { GLenum target = 0; // 4 GLuint id = 0; // 4 GLuint sampler = 0; // 4 - float anisotropy = 1.0f; // 4 + Handle ref; // 4 + math::half anisotropy = 1.0f; // 2 + int8_t baseLevel = 0x7f; // 1 + int8_t maxLevel = -1; // 1 }; // A sampler descriptor for ES2 @@ -131,6 +142,10 @@ private: }; static_assert(sizeof(Descriptor) <= 32); + template + static void updateTextureLod(OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept; + utils::FixedCapacityVector descriptors; // 16 utils::bitset64 dynamicBuffers; // 8 DescriptorSetLayoutHandle dslh; // 4 diff --git a/filament/backend/src/opengl/GLTexture.h b/filament/backend/src/opengl/GLTexture.h index 5e9460e17a..63a18a4d27 100644 --- a/filament/backend/src/opengl/GLTexture.h +++ b/filament/backend/src/opengl/GLTexture.h @@ -21,12 +21,20 @@ #include "gl_headers.h" +#include #include #include namespace filament::backend { +struct GLTextureRef { + GLTextureRef() = default; + uint16_t count = 1; + int8_t baseLevel = 127; + int8_t maxLevel = -1; +}; + struct GLTexture : public HwTexture { using HwTexture::HwTexture; struct GL { @@ -45,7 +53,7 @@ struct GLTexture : public HwTexture { uint8_t sidecarSamples : 4; uint8_t reserved1 : 3; } gl; - + mutable Handle ref; OpenGLPlatform::ExternalTexture* externalTexture = nullptr; }; diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 15e4692849..0db682a0b3 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -17,6 +17,7 @@ #include "OpenGLDriver.h" #include "CommandStreamDispatcher.h" +#include "GLTexture.h" #include "GLUtils.h" #include "OpenGLContext.h" #include "OpenGLDriverFactory.h" @@ -40,6 +41,7 @@ #include "private/backend/Dispatcher.h" #include "private/backend/DriverApi.h" +#include #include #include #include @@ -512,6 +514,10 @@ Handle OpenGLDriver::createTextureSwizzledS() noexcept { return initHandle(); } +Handle OpenGLDriver::createTextureViewS() noexcept { + return initHandle(); +} + Handle OpenGLDriver::importTextureS() noexcept { return initHandle(); } @@ -901,6 +907,52 @@ void OpenGLDriver::createTextureSwizzledR(Handle th, CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::createTextureViewR(Handle th, + Handle srch, uint8_t baseLevel, uint8_t levelCount) { + DEBUG_MARKER() + GLTexture const* const src = handle_cast(srch); + + FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE)) + << "TextureView can only be created on a SAMPLEABLE texture"; + + FILAMENT_CHECK_PRECONDITION(!src->gl.imported) + << "TextureView can't be created on imported textures"; + + if (!src->ref) { + // lazily create the ref handle, because most textures will never get a texture view + src->ref = initHandle(); + } + + GLTexture* t = construct(th, + src->target, + src->levels, + src->samples, + src->width, src->height, src->depth, + src->format, + src->usage); + + t->gl = src->gl; + t->gl.sidecarRenderBufferMS = 0; + t->gl.sidecarSamples = 1; + + auto srcBaseLevel = src->gl.baseLevel; + auto srcMaxLevel = src->gl.maxLevel; + if (srcBaseLevel > srcMaxLevel) { + srcBaseLevel = 0; + srcMaxLevel = 127; + } + t->gl.baseLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel); + t->gl.maxLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel + levelCount - 1); + + // increase reference count to this texture handle + t->ref = src->ref; + GLTextureRef* ref = handle_cast(t->ref); + assert_invariant(ref); + ref->count++; + + CHECK_GL_ERROR(utils::slog.e) +} + void OpenGLDriver::importTextureR(Handle th, intptr_t id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) { @@ -1305,14 +1357,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, rt->gl.resolve |= resolveFlags; - if (any(t->usage & TextureUsage::SAMPLEABLE)) { - // In a sense, drawing to a texture level is similar to calling setTextureData on it; in - // both cases, we update the base/max LOD to give shaders access to levels as they become - // available. Note that this can only expand the LOD range (never shrink it), and that - // users can override this range by calling setMinMaxLevels(). - updateTextureLodRange(t, (int8_t)binfo.level); - } - CHECK_GL_ERROR(utils::slog.e) CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER) } @@ -1653,16 +1697,35 @@ void OpenGLDriver::destroyTexture(Handle th) { if (th) { auto& gl = mContext; GLTexture* t = handle_cast(th); + if (UTILS_LIKELY(!t->gl.imported)) { if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) { - gl.unbindTexture(t->gl.target, t->gl.id); - if (UTILS_UNLIKELY(t->hwStream)) { - detachStream(t); + // drop a reference + uint16_t count = 0; + if (UTILS_UNLIKELY(t->ref)) { + // the common case is that we don't have a ref handle + GLTextureRef* const ref = handle_cast(t->ref); + count = --(ref->count); + if (count == 0) { + destruct(t->ref, ref); + } } - if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) { - mPlatform.destroyExternalImage(t->externalTexture); + if (count == 0) { + // if this was the last reference, we destroy the refcount as well as + // the GL texture name itself. + gl.unbindTexture(t->gl.target, t->gl.id); + if (UTILS_UNLIKELY(t->hwStream)) { + detachStream(t); + } + if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) { + mPlatform.destroyExternalImage(t->externalTexture); + } else { + glDeleteTextures(1, &t->gl.id); + } } else { - glDeleteTextures(1, &t->gl.id); + // The Handle is always destroyed. For extra precaution we also + // check that the GLTexture has a trivial destructor. + static_assert(std::is_trivially_destructible_v); } } else { assert_invariant(t->gl.target == GL_RENDERBUFFER); @@ -2361,28 +2424,6 @@ void OpenGLDriver::updateSamplerGroup(Handle sbh, BufferDescript // TODO: goes away } -void OpenGLDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { - DEBUG_MARKER() - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - auto& gl = mContext; - if (!gl.isES2()) { - GLTexture* t = handle_cast(th); - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); - gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); - - // Must fit within int8_t. - assert_invariant(minLevel <= 0x7f && maxLevel <= 0x7f); - - t->gl.baseLevel = (int8_t)minLevel; - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - - t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000 - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } -#endif -} - void OpenGLDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, @@ -2412,16 +2453,6 @@ void OpenGLDriver::generateMipmaps(Handle th) { bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); - t->gl.baseLevel = 0; - t->gl.maxLevel = static_cast(t->levels - 1); - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!gl.isES2()) { - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } -#endif - glGenerateMipmap(t->gl.target); CHECK_GL_ERROR(utils::slog.e) @@ -2531,21 +2562,6 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level, } } -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!gl.isES2()) { - // Update the base/max LOD, so we don't access undefined LOD. this allows the app to - // specify levels as they become available. - if (int8_t(level) < t->gl.baseLevel) { - t->gl.baseLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - } - if (int8_t(level) > t->gl.maxLevel) { - t->gl.maxLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } - } -#endif - scheduleDestroy(std::move(p)); CHECK_GL_ERROR(utils::slog.e) @@ -2632,21 +2648,6 @@ void OpenGLDriver::setCompressedTextureData(GLTexture* t, uint32_t level, } } -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!gl.isES2()) { - // Update the base/max LOD, so we don't access undefined LOD. this allows the app to - // specify levels as they become available. - if (int8_t(level) < t->gl.baseLevel) { - t->gl.baseLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - } - if (int8_t(level) > t->gl.maxLevel) { - t->gl.maxLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } - } -#endif - scheduleDestroy(std::move(p)); CHECK_GL_ERROR(utils::slog.e) @@ -3683,15 +3684,6 @@ void OpenGLDriver::blit( gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER); gl.unbindFramebuffer(GL_READ_FRAMEBUFFER); glDeleteFramebuffers(2, fbo); - - if (any(d->usage & TextureUsage::SAMPLEABLE)) { - // In a sense, blitting to a texture level is similar to calling setTextureData on it; in - // both cases, we update the base/max LOD to give shaders access to levels as they become - // available. Note that this can only expand the LOD range (never shrink it), and that - // users can override this range by calling setMinMaxLevels(). - updateTextureLodRange(d, int8_t(dstLevel)); - } - #endif } @@ -3762,29 +3754,6 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers, #endif } -void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept { -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - auto& gl = mContext; - if (!gl.isES2()) { - if (texture && any(texture->usage & TextureUsage::SAMPLEABLE)) { - if (targetLevel < texture->gl.baseLevel || targetLevel > texture->gl.maxLevel) { - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, texture); - gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); - if (targetLevel < texture->gl.baseLevel) { - texture->gl.baseLevel = targetLevel; - glTexParameteri(texture->gl.target, GL_TEXTURE_BASE_LEVEL, targetLevel); - } - if (targetLevel > texture->gl.maxLevel) { - texture->gl.maxLevel = targetLevel; - glTexParameteri(texture->gl.target, GL_TEXTURE_MAX_LEVEL, targetLevel); - } - } - CHECK_GL_ERROR(utils::slog.e) - } - } -#endif -} - void OpenGLDriver::bindPipeline(PipelineState const& state) { DEBUG_MARKER() auto& gl = mContext; @@ -3863,7 +3832,8 @@ void OpenGLDriver::updateDescriptors(utils::bitset8 invalidDescriptorSets) noexc ds->validate(mHandleAllocator, mCurrentSetLayout[set]); } #endif - ds->bind(context, boundProgram, set, entry.offsets.data(), offsetOnly[set]); + ds->bind(context, mHandleAllocator, boundProgram, + set, entry.offsets.data(), offsetOnly[set]); } }); mInvalidDescriptorSetBindings.clear(); diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index fda2b3bf20..0112d215b3 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -358,8 +358,6 @@ private: void detachStream(GLTexture* t) noexcept; void replaceStream(GLTexture* t, GLStream* stream) noexcept; - void updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept; - #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 // tasks executed on the main thread after the fence signaled void whenGpuCommandsComplete(const std::function& fn) noexcept; diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index eadb53fc92..46f515c0a2 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -17,6 +17,7 @@ #include "OpenGLProgram.h" #include "GLUtils.h" +#include "GLTexture.h" #include "OpenGLDriver.h" #include "ShaderCompilerService.h" @@ -24,8 +25,9 @@ #include #include -#include +#include #include +#include #include #include #include diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index a9e402e3ec..8bfd1ef22d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -90,9 +90,9 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, - VulkanStagePool& stagePool) { + VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) { VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator, - commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1, + commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT, stagePool, true /* heap allocated */); uint32_t black = 0; @@ -257,7 +257,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mTimestamps = std::make_unique(mPlatform->getDevice()); mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), - mContext, mAllocator, &mCommands, mStagePool); + mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool); mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands); mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture, @@ -553,7 +553,8 @@ void VulkanDriver::createTextureR(Handle th, SamplerType target, uint TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) { auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels, + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, + target, levels, format, samples, w, h, depth, usage, mStagePool); mResourceManager.acquire(vktexture); } @@ -565,8 +566,18 @@ void VulkanDriver::createTextureSwizzledR(Handle th, SamplerType targ TextureSwizzle swizzleArray[] = {r, g, b, a}; const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray); auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels, - format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap); + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, + target, levels, format, samples, w, h, depth, usage, mStagePool, + false /*heap allocated */, swizzleMap); + mResourceManager.acquire(vktexture); +} + +void VulkanDriver::createTextureViewR(Handle th, + Handle srch, uint8_t baseLevel, uint8_t levelCount) { + VulkanTexture const* src = mResourceAllocator.handle_cast(srch); + auto vktexture = mResourceAllocator.construct(th, + mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, + &mResourceAllocator, src, baseLevel, levelCount, mStagePool); mResourceManager.acquire(vktexture); } @@ -660,7 +671,8 @@ void VulkanDriver::createRenderTargetR(Handle rth, assert_invariant(tmin.x >= width && tmin.y >= height); auto renderTarget = mResourceAllocator.construct(rth, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height, + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, + width, height, samples, colorTargets, depthStencil, mStagePool); mResourceManager.acquire(renderTarget); } @@ -689,7 +701,7 @@ void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE); } auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, - mAllocator, &mCommands, mStagePool, nativeWindow, flags); + mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags); mResourceManager.acquire(swapChain); } @@ -702,7 +714,8 @@ void VulkanDriver::createSwapChainHeadlessR(Handle sch, uint32_t wi } assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions."); auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, - mAllocator, &mCommands, mStagePool, nullptr, flags, VkExtent2D{width, height}); + mAllocator, &mCommands, &mResourceAllocator, mStagePool, + nullptr, flags, VkExtent2D{width, height}); mResourceManager.acquire(swapChain); } @@ -750,6 +763,10 @@ Handle VulkanDriver::createTextureSwizzledS() noexcept { return mResourceAllocator.allocHandle(); } +Handle VulkanDriver::createTextureViewS() noexcept { + return mResourceAllocator.allocHandle(); +} + Handle VulkanDriver::importTextureS() noexcept { return mResourceAllocator.allocHandle(); } @@ -1128,11 +1145,6 @@ void VulkanDriver::resetBufferObject(Handle boh) { // This is only useful if updateBufferObjectUnsynchronized() is implemented unsynchronizedly. } -void VulkanDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { - auto texture = mResourceAllocator.handle_cast(th); - texture->setPrimaryRange(minLevel, maxLevel); -} - void VulkanDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, PixelBufferDescriptor&& data) { @@ -1218,7 +1230,6 @@ void VulkanDriver::generateMipmaps(Handle th) { srcw = dstw; srch = dsth; } while ((srcw > 1 || srch > 1) && level < t->levels); - t->setPrimaryRange(0, t->levels - 1); } void VulkanDriver::updateSamplerGroup(Handle sbh, diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 7a2041f9fd..e37b80d27a 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -320,6 +320,7 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) { VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, uint32_t width, uint32_t height, uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], VulkanAttachment depthStencil[2], VulkanStagePool& stagePool) @@ -352,6 +353,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica if (UTILS_UNLIKELY(!msTexture)) { // TODO: This should be allocated with the ResourceAllocator. msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands, + handleAllocator, texture->target, ((VulkanTexture const*) texture)->levels, texture->format, samples, texture->width, texture->height, texture->depth, texture->usage, stagePool, true /* heap allocated */); @@ -381,7 +383,8 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica VulkanTexture* msTexture = depthTexture->getSidecar(); if (UTILS_UNLIKELY(!msTexture)) { msTexture = new VulkanTexture(device, physicalDevice, context, allocator, - commands, depthTexture->target, msLevel, depthTexture->format, samples, + commands, handleAllocator, + depthTexture->target, msLevel, depthTexture->format, samples, depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage, stagePool, true /* heap allocated */); depthTexture->setSidecar(msTexture); diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index bb1f351fd1..65550d7648 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -297,7 +297,9 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource { // Creates an offscreen render target. VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, - VulkanCommands* commands, uint32_t width, uint32_t height, + VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + uint32_t width, uint32_t height, uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], VulkanAttachment depthStencil[2], VulkanStagePool& stagePool); diff --git a/filament/backend/src/vulkan/VulkanSwapChain.cpp b/filament/backend/src/vulkan/VulkanSwapChain.cpp index 0cf75d0f13..4989b925a3 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.cpp +++ b/filament/backend/src/vulkan/VulkanSwapChain.cpp @@ -26,12 +26,14 @@ using namespace utils; namespace filament::backend { VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, - VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool, + VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, + VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags, VkExtent2D extent) : VulkanResource(VulkanResourceType::SWAP_CHAIN), mPlatform(platform), mCommands(commands), mAllocator(allocator), + mHandleAllocator(handleAllocator), mStagePool(stagePool), mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow), mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize), @@ -62,12 +64,12 @@ void VulkanSwapChain::update() { VkDevice const device = mPlatform->getDevice(); for (auto const color: bundle.colors) { - mColors.push_back(std::make_unique(device, mAllocator, mCommands, color, - bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, + mColors.push_back(std::make_unique(device, mAllocator, mCommands, mHandleAllocator, + color, bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */)); } - mDepth = std::make_unique(device, mAllocator, mCommands, bundle.depth, - bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, + mDepth = std::make_unique(device, mAllocator, mCommands, mHandleAllocator, + bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */); mExtent = bundle.extent; diff --git a/filament/backend/src/vulkan/VulkanSwapChain.h b/filament/backend/src/vulkan/VulkanSwapChain.h index 80886f8b1b..b3e49c044e 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.h +++ b/filament/backend/src/vulkan/VulkanSwapChain.h @@ -36,11 +36,13 @@ namespace filament::backend { struct VulkanHeadlessSwapChain; struct VulkanSurfaceSwapChain; +class VulkanResourceAllocator; // A wrapper around the platform implementation of swapchain. struct VulkanSwapChain : public HwSwapChain, VulkanResource { VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator, - VulkanCommands* commands, VulkanStagePool& stagePool, + VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, + VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0}); ~VulkanSwapChain(); @@ -80,6 +82,7 @@ private: VulkanPlatform* mPlatform; VulkanCommands* mCommands; VmaAllocator mAllocator; + VulkanResourceAllocator* const mHandleAllocator; VulkanStagePool& mStagePool; bool const mHeadless; bool const mFlushAndWaitOnResize; diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index 3d021daff0..d673454843 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -15,6 +15,7 @@ */ #include "VulkanMemory.h" +#include "VulkanResourceAllocator.h" #include "VulkanTexture.h" #include "VulkanUtility.h" @@ -28,50 +29,78 @@ using namespace bluevk; namespace filament::backend { -VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + +VulkanTextureState::VulkanTextureState( + VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanStagePool& stagePool, + VkFormat format, VkImageViewType viewType, VkComponentMapping swizzle) + : VulkanResource(VulkanResourceType::HEAP_ALLOCATED), + mVkFormat(format), + mViewType(viewType), + mSwizzle(swizzle), + mStagePool(stagePool), + mDevice(device), + mAllocator(allocator), + mCommands(commands) { +} + +VulkanTextureState* VulkanTexture::getSharedState() { + VulkanTextureState* state = mAllocator->handle_cast(mState); + return state; +} + +VulkanTextureState const* VulkanTexture::getSharedState() const { + VulkanTextureState const* state = mAllocator->handle_cast(mState); + return state; +} + +VulkanTexture::VulkanTexture( + VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated) - : HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED, - tusage), - VulkanResource( - heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), - mVkFormat(format), - mViewType(imgutil::getViewType(target)), - mSwizzle({}), - mTextureImage(image), - mFullViewRange{ - .aspectMask = getImageAspect(), - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - mPrimaryViewRange(mFullViewRange), - mStagePool(stagePool), - mDevice(device), - mAllocator(allocator), - mCommands(commands) {} + : HwTexture( + SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED, tusage), + VulkanResource( + heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), + mAllocator(handleAllocator), + mState(handleAllocator->initHandle( + device, allocator, commands, stagePool, + format, imgutil::getViewType(SamplerType::SAMPLER_2D), VkComponentMapping{})), + + mFullViewRange{ + .aspectMask = getImageAspect(), + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + mPrimaryViewRange(mFullViewRange) +{ + auto* const state = handleAllocator->handle_cast(mState); + state->mTextureImage = image; +} VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated, VkComponentMapping swizzle) : HwTexture(target, levels, samples, w, h, depth, tformat, tusage), VulkanResource( heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), - mVkFormat(backend::getVkFormat(tformat)), - mViewType(imgutil::getViewType(target)), - mSwizzle(swizzle), - mStagePool(stagePool), - mDevice(device), - mAllocator(allocator), - mCommands(commands) { + mAllocator(handleAllocator), + mState(handleAllocator->initHandle( + device, allocator, commands, stagePool, + backend::getVkFormat(tformat), imgutil::getViewType(target), swizzle)) +{ + auto* const state = handleAllocator->handle_cast(mState); // Create an appropriately-sized device-only VkImage, but do not fill it yet. VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D, - .format = mVkFormat, + .format = state->mVkFormat, .extent = {w, h, depth}, .mipLevels = levels, .arrayLayers = 1, @@ -147,7 +176,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, // any kind of attachment (color or depth). const auto& limits = context.getPhysicalDeviceLimits(); if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) { - samples = reduceSampleCount(samples, isVkDepthFormat(mVkFormat) + samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat) ? limits.sampledImageDepthSampleCounts : limits.sampledImageColorSampleCounts); } @@ -161,12 +190,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, this->samples = samples; imageInfo.samples = (VkSampleCountFlagBits) samples; - VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage); + VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage); if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) { FVK_LOGD << "vkCreateImage: " - << "image = " << mTextureImage << ", " + << "image = " << state->mTextureImage << ", " << "result = " << error << ", " - << "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", " + << "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", " << "extent = " << w << "x" << h << "x"<< depth << ", " << "mipLevels = " << int(levels) << ", " << "TextureUsage = " << static_cast(usage) << ", " @@ -175,13 +204,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, << "type = " << imageInfo.imageType << ", " << "flags = " << imageInfo.flags << ", " << "target = " << static_cast(target) <<", " - << "format = " << mVkFormat << utils::io::endl; + << "format = " << state->mVkFormat << utils::io::endl; } FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image."; // Allocate memory for the VkImage and bind it. VkMemoryRequirements memReqs = {}; - vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs); + vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs); uint32_t memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); @@ -194,9 +223,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, .allocationSize = memReqs.size, .memoryTypeIndex = memoryTypeIndex, }; - error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory); + error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory); FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory."; - error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0); + error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->mTextureImageMemory, 0); FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image."; uint32_t layerCount = 0; @@ -224,20 +253,44 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, mPrimaryViewRange = mFullViewRange; // Go ahead and create the primary image view. - getImageView(mPrimaryViewRange, mViewType, mSwizzle); + getImageView(mPrimaryViewRange, state->mViewType, state->mSwizzle); - VulkanCommandBuffer& commandsBuf = mCommands->get(); + VulkanCommandBuffer& commandsBuf = state->mCommands->get(); commandsBuf.acquire(this); transitionLayout(&commandsBuf, mFullViewRange, imgutil::getDefaultLayout(imageInfo.usage)); } +VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, + VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount, + VulkanStagePool& stagePool) + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + VulkanResource(VulkanResourceType::TEXTURE), + mAllocator(handleAllocator) +{ + mState = src->mState; + auto* state = handleAllocator->handle_cast(mState); + state->refs++; + mFullViewRange = src->mFullViewRange; + mPrimaryViewRange = src->mPrimaryViewRange; + mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel; + mPrimaryViewRange.levelCount = levelCount; +} + VulkanTexture::~VulkanTexture() { - if (mTextureImageMemory != VK_NULL_HANDLE) { - vkDestroyImage(mDevice, mTextureImage, VKALLOC); - vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC); - } - for (auto entry : mCachedImageViews) { - vkDestroyImageView(mDevice, entry.second, VKALLOC); + auto* const state = mAllocator->handle_cast(mState); + state->refs--; + if (state->refs == 0) { + if (state->mTextureImageMemory != VK_NULL_HANDLE) { + vkDestroyImage(state->mDevice, state->mTextureImage, VKALLOC); + vkFreeMemory(state->mDevice, state->mTextureImageMemory, VKALLOC); + } + for (auto entry: state->mCachedImageViews) { + vkDestroyImageView(state->mDevice, entry.second, VKALLOC); + } + mAllocator->destruct(mState); } } @@ -246,6 +299,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt assert_invariant(width <= this->width && height <= this->height); assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP || target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1)); + auto* const state = mAllocator->handle_cast(mState); const PixelBufferDescriptor* hostData = &data; PixelBufferDescriptor reshapedData; @@ -258,7 +312,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt // If format conversion is both required and supported, use vkCmdBlitImage. const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type); - const VkFormat deviceFormat = getVkFormatLinear(mVkFormat); + const VkFormat deviceFormat = getVkFormatLinear(state->mVkFormat); if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) { assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 && "Offsets not yet supported when format conversion is required."); @@ -270,14 +324,14 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt // Otherwise, use vkCmdCopyBufferToImage. void* mapped = nullptr; - VulkanStage const* stage = mStagePool.acquireStage(hostData->size); + VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size); assert_invariant(stage->memory); - vmaMapMemory(mAllocator, stage->memory, &mapped); + vmaMapMemory(state->mAllocator, stage->memory, &mapped); memcpy(mapped, hostData->buffer, hostData->size); - vmaUnmapMemory(mAllocator, stage->memory); - vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size); + vmaUnmapMemory(state->mAllocator, stage->memory); + vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size); - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = state->mCommands->get(); VkCommandBuffer const cmdbuf = commands.buffer(); commands.acquire(this); @@ -325,22 +379,23 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt transitionLayout(&commands, transitionRange, newLayout); - vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mTextureImage, newVkLayout, 1, ©Region); + vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, ©Region); transitionLayout(&commands, transitionRange, nextLayout); } void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height, uint32_t depth, uint32_t miplevel) { + auto* const state = mAllocator->handle_cast(mState); void* mapped = nullptr; VulkanStageImage const* stage - = mStagePool.acquireImage(hostData.format, hostData.type, width, height); - vmaMapMemory(mAllocator, stage->memory, &mapped); + = state->mStagePool.acquireImage(hostData.format, hostData.type, width, height); + vmaMapMemory(state->mAllocator, stage->memory, &mapped); memcpy(mapped, hostData.buffer, hostData.size); - vmaUnmapMemory(mAllocator, stage->memory); - vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size); + vmaUnmapMemory(state->mAllocator, stage->memory); + vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size); - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = state->mCommands->get(); VkCommandBuffer const cmdbuf = commands.buffer(); commands.acquire(this); @@ -365,18 +420,11 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u transitionLayout(&commands, range, newLayout); vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC), - mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST); + state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST); transitionLayout(&commands, range, oldLayout); } -void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel) { - maxMiplevel = filament::math::min(int(maxMiplevel), int(this->levels - 1)); - mPrimaryViewRange.baseMipLevel = minMiplevel; - mPrimaryViewRange.levelCount = maxMiplevel - minMiplevel + 1; - getImageView(mPrimaryViewRange, mViewType, mSwizzle); -} - VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) { // Attachments should only have one mipmap level and one layer. range.levelCount = 1; @@ -385,35 +433,38 @@ VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) { } VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) { - return getImageView(range, type, mSwizzle); + auto* const state = mAllocator->handle_cast(mState); + return getImageView(range, type, state->mSwizzle); } VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) { - ImageViewKey const key {range, viewType, swizzle}; - auto iter = mCachedImageViews.find(key); - if (iter != mCachedImageViews.end()) { + auto* const state = mAllocator->handle_cast(mState); + VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle }; + auto iter = state->mCachedImageViews.find(key); + if (iter != state->mCachedImageViews.end()) { return iter->second; } VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, - .image = mTextureImage, + .image = state->mTextureImage, .viewType = viewType, - .format = mVkFormat, + .format = state->mVkFormat, .components = swizzle, .subresourceRange = range, }; VkImageView imageView; - vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView); - mCachedImageViews.emplace(key, imageView); + vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView); + state->mCachedImageViews.emplace(key, imageView); return imageView; } VkImageAspectFlags VulkanTexture::getImageAspect() const { // Helper function in VulkanUtility - return filament::backend::getImageAspect(mVkFormat); + auto* const state = mAllocator->handle_cast(mState); + return filament::backend::getImageAspect(state->mVkFormat); } void VulkanTexture::transitionLayout(VulkanCommandBuffer* commands, @@ -425,6 +476,7 @@ void VulkanTexture::transitionLayout( VkCommandBuffer cmdbuf, std::shared_ptr fence, const VkImageSubresourceRange& range, VulkanLayout newLayout) { + auto* const state = mAllocator->handle_cast(mState); VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel); uint32_t const firstLayer = range.baseArrayLayer; @@ -455,7 +507,7 @@ void VulkanTexture::transitionLayout( continue; } hasTransitions = hasTransitions || imgutil::transitionLayout(cmdbuf, { - .image = mTextureImage, + .image = state->mTextureImage, .oldLayout = layout, .newLayout = newLayout, .subresources = { @@ -470,7 +522,7 @@ void VulkanTexture::transitionLayout( } } else if (newLayout != oldLayout) { hasTransitions = imgutil::transitionLayout(cmdbuf, { - .image = mTextureImage, + .image = state->mTextureImage, .oldLayout = oldLayout, .newLayout = newLayout, .subresources = range, @@ -478,7 +530,7 @@ void VulkanTexture::transitionLayout( } if (hasTransitions) { - mTransitionFence = fence; + state->mTransitionFence = fence; setLayout(range, newLayout); #if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) @@ -499,6 +551,7 @@ void VulkanTexture::transitionLayout( } void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout) { + auto* const state = mAllocator->handle_cast(mState); uint32_t const firstLayer = range.baseArrayLayer; uint32_t const lastLayer = firstLayer + range.layerCount; uint32_t const firstLevel = range.baseMipLevel; @@ -511,24 +564,25 @@ void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) { uint32_t const first = (layer << 16) | firstLevel; uint32_t const last = (layer << 16) | lastLevel; - mSubresourceLayouts.clear(first, last); + state->mSubresourceLayouts.clear(first, last); } } else { for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) { uint32_t const first = (layer << 16) | firstLevel; uint32_t const last = (layer << 16) | lastLevel; - mSubresourceLayouts.add(first, last, newLayout); + state->mSubresourceLayouts.add(first, last, newLayout); } } } VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const { assert_invariant(level <= 0xffff && layer <= 0xffff); + auto* const state = mAllocator->handle_cast(mState); const uint32_t key = (layer << 16) | level; - if (!mSubresourceLayouts.has(key)) { + if (!state->mSubresourceLayouts.has(key)) { return VulkanLayout::UNDEFINED; } - return mSubresourceLayouts.get(key); + return state->mSubresourceLayouts.get(key); } #if FVK_ENABLED(FVK_DEBUG_TEXTURE) diff --git a/filament/backend/src/vulkan/VulkanTexture.h b/filament/backend/src/vulkan/VulkanTexture.h index 6b19a96832..4bdf0c4e23 100644 --- a/filament/backend/src/vulkan/VulkanTexture.h +++ b/filament/backend/src/vulkan/VulkanTexture.h @@ -30,95 +30,13 @@ namespace filament::backend { -struct VulkanTexture : public HwTexture, VulkanResource { - // Standard constructor for user-facing textures. - VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, - VmaAllocator allocator, VulkanCommands* commands, SamplerType target, uint8_t levels, - TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, - TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false, - VkComponentMapping swizzle = {}); +class VulkanResourceAllocator; - // Specialized constructor for internally created textures (e.g. from a swap chain) - // The texture will never destroy the given VkImage, but it does manages its subresources. - VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image, - VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage, - VulkanStagePool& stagePool, bool heapAllocated = false); +struct VulkanTextureState : public VulkanResource { + VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanStagePool& stagePool, + VkFormat format, VkImageViewType viewType, VkComponentMapping swizzle); - ~VulkanTexture(); - - // Uploads data into a subregion of a 2D or 3D texture. - void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height, - uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel); - - // Returns the primary image view, which is used for shader sampling. - VkImageView getPrimaryImageView() { - return getImageView(mPrimaryViewRange, mViewType, mSwizzle); - } - - VkImageViewType getViewType() const { return mViewType; } - - // Sets the min/max range of miplevels in the primary image view. - void setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel); - - VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; } - - VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; } - - VulkanLayout getPrimaryImageLayout() const { - return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel); - } - - // Gets or creates a cached VkImageView for a single subresource that can be used as a render - // target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D - // and the identity swizzle. - VkImageView getAttachmentView(VkImageSubresourceRange range); - - // This is a workaround for the first few frames where we're waiting for the texture to actually - // be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding - // imageView needs to be of the right type. Hence, we provide an option to indicate the - // view type. Swizzle option does not matter in this case. - VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type); - - VkFormat getVkFormat() const { return mVkFormat; } - VkImage getVkImage() const { return mTextureImage; } - - VulkanLayout getLayout(uint32_t layer, uint32_t level) const; - - void setSidecar(VulkanTexture* sidecar) { - mSidecarMSAA.reset(sidecar); - } - - VulkanTexture* getSidecar() const { - return mSidecarMSAA.get(); - } - - void transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range, - VulkanLayout newLayout); - - void transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr fence, - const VkImageSubresourceRange& range, VulkanLayout newLayout); - - // Returns the preferred data plane of interest for all image views. - // For now this always returns either DEPTH or COLOR. - VkImageAspectFlags getImageAspect() const; - - // For implicit transition like the end of a render pass, we need to be able to set the layout - // manually (outside of calls to transitionLayout). - void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout); - - void setPrimaryLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout); - - bool transitionReady() { - auto res = !mTransitionFence || mTransitionFence->getStatus() == VK_SUCCESS; - mTransitionFence.reset(); - return res; - } - -#if FVK_ENABLED(FVK_DEBUG_TEXTURE) - void print() const; -#endif - -private: struct ImageViewKey { VkImageSubresourceRange range; // 4 * 5 bytes VkImageViewType type; // 4 bytes @@ -140,31 +58,20 @@ private: using ImageViewHash = utils::hash::MurmurHashFn; - // Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and - // swizzle (or not). - VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType, - VkComponentMapping swizzle); - - void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height, - uint32_t depth, uint32_t miplevel); + uint32_t refs = 1; // The texture with the sidecar owns the sidecar. std::unique_ptr mSidecarMSAA; + VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE; + const VkFormat mVkFormat; const VkImageViewType mViewType; const VkComponentMapping mSwizzle; VkImage mTextureImage = VK_NULL_HANDLE; - VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE; // Track the image layout of each subresource using a sparse range map. utils::RangeMap mSubresourceLayouts; - VkImageSubresourceRange mFullViewRange; - - // Track the range of subresources that define the "primary" image view, which is the special - // image view that gets bound to an actual texture sampler. - VkImageSubresourceRange mPrimaryViewRange; - std::unordered_map mCachedImageViews; VulkanStagePool& mStagePool; VkDevice mDevice; @@ -173,6 +80,139 @@ private: std::shared_ptr mTransitionFence; }; + +struct VulkanTexture : public HwTexture, VulkanResource { + // Standard constructor for user-facing textures. + VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, + VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + SamplerType target, uint8_t levels, + TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, + TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false, + VkComponentMapping swizzle = {}); + + // Specialized constructor for internally created textures (e.g. from a swap chain) + // The texture will never destroy the given VkImage, but it does manages its subresources. + VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VkImage image, + VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage, + VulkanStagePool& stagePool, bool heapAllocated = false); + + // constructor for creating a texture view + VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, + VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount, + VulkanStagePool& stagePool); + + ~VulkanTexture(); + + // Uploads data into a subregion of a 2D or 3D texture. + void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height, + uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel); + + // Returns the primary image view, which is used for shader sampling. + VkImageView getPrimaryImageView() { + VulkanTextureState* state = getSharedState(); + return getImageView(mPrimaryViewRange, state->mViewType, state->mSwizzle); + } + + VkImageViewType getViewType() const { + VulkanTextureState const* state = getSharedState(); + return state->mViewType; + } + + VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; } + + VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; } + + VulkanLayout getPrimaryImageLayout() const { + return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel); + } + + // Gets or creates a cached VkImageView for a single subresource that can be used as a render + // target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D + // and the identity swizzle. + VkImageView getAttachmentView(VkImageSubresourceRange range); + + // This is a workaround for the first few frames where we're waiting for the texture to actually + // be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding + // imageView needs to be of the right type. Hence, we provide an option to indicate the + // view type. Swizzle option does not matter in this case. + VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type); + + VkFormat getVkFormat() const { + VulkanTextureState const* state = getSharedState(); + return state->mVkFormat; + } + VkImage getVkImage() const { + VulkanTextureState const* state = getSharedState(); + return state->mTextureImage; + } + + VulkanLayout getLayout(uint32_t layer, uint32_t level) const; + + void setSidecar(VulkanTexture* sidecar) { + VulkanTextureState* state = getSharedState(); + state->mSidecarMSAA.reset(sidecar); + } + + VulkanTexture* getSidecar() const { + VulkanTextureState const* state = getSharedState(); + return state->mSidecarMSAA.get(); + } + + void transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range, + VulkanLayout newLayout); + + void transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr fence, + const VkImageSubresourceRange& range, VulkanLayout newLayout); + + // Returns the preferred data plane of interest for all image views. + // For now this always returns either DEPTH or COLOR. + VkImageAspectFlags getImageAspect() const; + + // For implicit transition like the end of a render pass, we need to be able to set the layout + // manually (outside of calls to transitionLayout). + void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout); + + void setPrimaryLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout); + + bool transitionReady() { + VulkanTextureState* state = getSharedState(); + auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS; + state->mTransitionFence.reset(); + return res; + } + +#if FVK_ENABLED(FVK_DEBUG_TEXTURE) + void print() const; +#endif + +private: + VulkanTextureState* getSharedState(); + VulkanTextureState const* getSharedState() const; + + // Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and + // swizzle (or not). + VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType, + VkComponentMapping swizzle); + + void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height, + uint32_t depth, uint32_t miplevel); + + VulkanResourceAllocator* const mAllocator; + + Handle mState; + + VkImageSubresourceRange mFullViewRange; + + // Track the range of subresources that define the "primary" image view, which is the special + // image view that gets bound to an actual texture sampler. + VkImageSubresourceRange mPrimaryViewRange; +}; + } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANTEXTURE_H diff --git a/filament/backend/test/test_FeedbackLoops.cpp b/filament/backend/test/test_FeedbackLoops.cpp index 8d49ef2acd..bb7a83416a 100644 --- a/filament/backend/test/test_FeedbackLoops.cpp +++ b/filament/backend/test/test_FeedbackLoops.cpp @@ -170,7 +170,6 @@ TEST_F(BackendTest, FeedbackLoops) { DescriptorFlags::NONE, 0 }}}); - DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); TrianglePrimitive const triangle(getDriverApi()); @@ -178,17 +177,10 @@ TEST_F(BackendTest, FeedbackLoops) { auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE; Handle const texture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage); - api.updateDescriptorSetTexture(descriptorSet, 0, texture, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST - }); // Create ubo auto ubuffer = api.createBufferObject(sizeof(MaterialParams), BufferObjectBinding::UNIFORM, BufferUsage::STATIC); - api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams)); - - api.bindDescriptorSet(descriptorSet, 1, {}); // Create a RenderTarget for each miplevel. Handle renderTargets[kNumLevels]; @@ -237,7 +229,16 @@ TEST_F(BackendTest, FeedbackLoops) { const uint32_t sourceLevel = targetLevel - 1; params.viewport.width = kTexWidth >> targetLevel; params.viewport.height = kTexHeight >> targetLevel; - getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel); + + auto textureView = api.createTextureView(texture, sourceLevel, 1); + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + api.updateDescriptorSetTexture(descriptorSet, 0, textureView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); + api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams)); + api.bindDescriptorSet(descriptorSet, 1, {}); + uploadUniforms(getDriverApi(), ubuffer, { .fbWidth = float(params.viewport.width), .fbHeight = float(params.viewport.height), @@ -246,6 +247,8 @@ TEST_F(BackendTest, FeedbackLoops) { api.beginRenderPass(renderTargets[targetLevel], params); api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); + api.destroyTexture(textureView); + api.destroyDescriptorSet(descriptorSet); } // Upsample passes @@ -256,7 +259,16 @@ TEST_F(BackendTest, FeedbackLoops) { const uint32_t sourceLevel = targetLevel + 1; params.viewport.width = kTexWidth >> targetLevel; params.viewport.height = kTexHeight >> targetLevel; - getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel); + + auto textureView = api.createTextureView(texture, sourceLevel, 1); + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + api.updateDescriptorSetTexture(descriptorSet, 0, textureView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); + api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams)); + api.bindDescriptorSet(descriptorSet, 1, {}); + uploadUniforms(getDriverApi(), ubuffer, { .fbWidth = float(params.viewport.width), .fbHeight = float(params.viewport.height), @@ -265,10 +277,10 @@ TEST_F(BackendTest, FeedbackLoops) { api.beginRenderPass(renderTargets[targetLevel], params); api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); + api.destroyTexture(textureView); + api.destroyDescriptorSet(descriptorSet); } - getDriverApi().setMinMaxLevels(texture, 0, 0x7f); - // Read back the render target corresponding to the base level. // // NOTE: Calling glReadPixels on any miplevel other than the base level @@ -285,7 +297,6 @@ TEST_F(BackendTest, FeedbackLoops) { getDriver().purge(); } - api.destroyDescriptorSet(descriptorSet); api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyProgram(program); api.destroySwapChain(swapChain); diff --git a/filament/backend/test/test_MipLevels.cpp b/filament/backend/test/test_MipLevels.cpp index 94944f797e..e0c41fb172 100644 --- a/filament/backend/test/test_MipLevels.cpp +++ b/filament/backend/test/test_MipLevels.cpp @@ -75,7 +75,7 @@ namespace test { using namespace filament; using namespace filament::backend; -TEST_F(BackendTest, SetMinMaxLevel) { +TEST_F(BackendTest, TextureViewLod) { auto& api = getDriverApi(); api.startCapture(0); @@ -116,7 +116,9 @@ TEST_F(BackendTest, SetMinMaxLevel) { DescriptorFlags::NONE, 0 }}}); - DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + DescriptorSetHandle descriptorSet[2]; + descriptorSet[0] = api.createDescriptorSet(descriptorSetLayout); + descriptorSet[1] = api.createDescriptorSet(descriptorSetLayout); // Create a texture that has 4 mip levels. Each level is a different color. // Level 0: 128x128 (red) @@ -161,7 +163,7 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Level 1: 64x64 (green) <-- base // Level 2: 32x32 (blue) <--- white triangle rendered // Level 3: 16x16 (yellow) <-- max - api.setMinMaxLevels(texture, 1, 3); + auto texture13 = api.createTextureView(texture, 1, 3); // Render a white triangle into level 2. // We specify mip level 2, because minMaxLevels has no effect when rendering into a texture. @@ -200,11 +202,11 @@ TEST_F(BackendTest, SetMinMaxLevel) { state.rasterState.depthFunc = SamplerCompareFunc::A; state.rasterState.culling = CullingMode::NONE; - api.updateDescriptorSetTexture(descriptorSet, 0, texture, { + api.updateDescriptorSetTexture(descriptorSet[0], 0, texture13, { .filterMag = SamplerMagFilter::NEAREST, .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); - api.bindDescriptorSet(descriptorSet, 0, {}); + api.bindDescriptorSet(descriptorSet[0], 0, {}); // Render a triangle to the screen, sampling from mip level 1. // Because the min level is 1, the result color should be the white triangle drawn in the @@ -216,7 +218,13 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Adjust the base mip to 2. // Note that this is done without another call to updateSamplerGroup. - api.setMinMaxLevels(texture, 2, 3); + auto texture22 = api.createTextureView(texture, 2, 2); + + api.updateDescriptorSetTexture(descriptorSet[1], 0, texture22, { + .filterMag = SamplerMagFilter::NEAREST, + .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); + + api.bindDescriptorSet(descriptorSet[1], 0, {}); // Render a second, smaller, triangle, again sampling from mip level 1. // This triangle should be yellow striped. @@ -241,9 +249,12 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Cleanup. api.destroySwapChain(swapChain); api.destroyRenderTarget(renderTarget); - api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSet(descriptorSet[0]); + api.destroyDescriptorSet(descriptorSet[1]); api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyTexture(texture); + api.destroyTexture(texture13); + api.destroyTexture(texture22); api.destroyProgram(whiteProgram); api.destroyProgram(textureProgram); } diff --git a/filament/docs/Vulkan.md.html b/filament/docs/Vulkan.md.html index 80db49db33..2a8902f345 100644 --- a/filament/docs/Vulkan.md.html +++ b/filament/docs/Vulkan.md.html @@ -182,7 +182,7 @@ updated several times; each time it undergoes these two transitions. If a shader samples from a texture whose mipmaps are only partially loaded, we might see validation warnings about how some subresources are in an UNDEFINED layout. However if we are properly using -the `setMinMaxLevels` driver API, then the Vulkan backend will not bind those particular +the `createTextureView` driver API, then the Vulkan backend will not bind those particular subresources, so validation should not complain. (2) **Writeable Color Textures** diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 44a4231604..7c70df46b8 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -533,14 +533,14 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph auto in = resources.getTexture(data.depth); auto& material = getPostProcessMaterial("mipmapDepth"); FMaterialInstance* const mi = material.getMaterialInstance(mEngine); - mi->setParameter("depth", in, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); // The first mip already exists, so we process n-1 lods for (size_t level = 0; level < levelCount - 1; level++) { auto out = resources.getRenderPassInfo(level); - driver.setMinMaxLevels(in, level, level); + auto th = driver.createTextureView(in, level, 1); + mi->setParameter("depth", th, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); commitAndRender(out, material, driver); + driver.destroyTexture(th); } - driver.setMinMaxLevels(in, 0, levelCount - 1); }); return { depth, structurePass->picking }; @@ -1644,27 +1644,25 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, auto const& material = getPostProcessMaterial("dofMipmap"); FMaterialInstance* const mi = material.getMaterialInstance(mEngine); - mi->setParameter("color", inOutColor, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); - mi->setParameter("coc", inOutCoc, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); - mi->commit(driver); - mi->use(driver); auto const pipeline = material.getPipelineState(mEngine, variant); for (size_t level = 0 ; level < mipmapCount - 1u ; level++) { const float w = FTexture::valueForLevel(level, desc.width); const float h = FTexture::valueForLevel(level, desc.height); - auto const& out = resources.getRenderPassInfo(data.rp[level]); - driver.setMinMaxLevels(inOutColor, level, level); - driver.setMinMaxLevels(inOutCoc, level, level); + auto inColor = driver.createTextureView(inOutColor, level, 1); + auto inCoc = driver.createTextureView(inOutCoc, level, 1); + mi->setParameter("color", inColor, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); + mi->setParameter("coc", inCoc, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres? mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h }); mi->commit(driver); + mi->use(driver); render(out, pipeline, driver); + driver.destroyTexture(inColor); + driver.destroyTexture(inCoc); } - driver.setMinMaxLevels(inOutColor, 0, mipmapCount - 1u); - driver.setMinMaxLevels(inOutCoc, 0, mipmapCount - 1u); }); /* @@ -2084,17 +2082,6 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, auto* mi9 = material9.getMaterialInstance(mEngine); auto* mi13 = material13.getMaterialInstance(mEngine); - mi9->setParameter("source", hwOut, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - - mi13->setParameter("source", hwOut, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - - mi9->commit(driver); - mi13->commit(driver); - // PipelineState for both materials should be the same auto const pipeline = material9.getPipelineState(mEngine); @@ -2107,11 +2094,15 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, // 9 samples filter. auto vp = resources.getRenderPassInfo(data.outRT[i-1]).params.viewport; auto* const mi = (vp.width & 1 || vp.height & 1) ? mi13 : mi9; + auto hwOutView = driver.createTextureView(hwOut, i - 1, 1); + mi->setParameter("source", hwOutView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); + mi->commit(driver); mi->use(driver); - driver.setMinMaxLevels(hwOut, i - 1, i - 1); // this offsets baseLevel to i-1 render(hwDstRT, pipeline, driver); + driver.destroyTexture(hwOutView); } - driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1); }); // output of bloom downsample pass becomes input of next (flare) pass @@ -2135,11 +2126,6 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, auto const& material = getPostProcessMaterial("bloomUpsample"); auto* mi = material.getMaterialInstance(mEngine); - mi->setParameter("source", hwOut, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); - mi->commit(driver); - mi->use(driver); auto pipeline = material.getPipelineState(mEngine); pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; @@ -2151,12 +2137,16 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE; auto w = FTexture::valueForLevel(i - 1, outDesc.width); auto h = FTexture::valueForLevel(i - 1, outDesc.height); + auto hwOutView = driver.createTextureView(hwOut, i, 1); mi->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h }); + mi->setParameter("source", hwOutView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); mi->commit(driver); - driver.setMinMaxLevels(hwOut, i, i); // this offsets baseLevel to i + mi->use(driver); render(hwDstRT, pipeline, driver); + driver.destroyTexture(hwOutView); } - driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1); }); return { bloomUpsamplePass->out, flare }; @@ -2366,7 +2356,7 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, bloomStrength = clamp(bloomOptions.strength, 0.0f, 1.0f); if (bloomOptions.dirt) { FTexture* fdirt = downcast(bloomOptions.dirt); - FrameGraphTexture const frameGraphTexture{ .handle = fdirt->getHwHandle() }; + FrameGraphTexture const frameGraphTexture{ .handle = fdirt->getHwHandleForSampling() }; bloomDirt = fg.import("dirt", { .width = (uint32_t)fdirt->getWidth(0u), .height = (uint32_t)fdirt->getHeight(0u), @@ -3260,7 +3250,7 @@ FrameGraphId PostProcessManager::resolveDepth(FrameGraph& fg, FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg, FrameGraphId input, uint8_t layer, size_t level, - math::float4 clearColor, bool finalize) noexcept { + math::float4 clearColor) noexcept { struct VsmMipData { FrameGraphId in; @@ -3284,7 +3274,7 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { - auto in = resources.getTexture(data.in); + auto in = driver.createTextureView(resources.getTexture(data.in), level, 1); auto out = resources.getRenderPassInfo(); auto const& inDesc = resources.getDescriptor(data.in); @@ -3292,8 +3282,6 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg assert_invariant(width == inDesc.height); int const dim = width >> (level + 1); - driver.setMinMaxLevels(in, level, level); - auto& material = getPostProcessMaterial("vsmMipmap"); // When generating shadow map mip levels, we want to preserve the 1 texel border. @@ -3311,10 +3299,7 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg mi->commit(driver); mi->use(driver); render(out, pipeline, scissor, driver); - - if (finalize) { - driver.setMinMaxLevels(in, 0, level); - } + driver.destroyTexture(in); // `in` is just a view on `data.in` }); return depthMipmapPass->in; diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 7bed969cc9..e3db6fd994 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -296,7 +296,7 @@ public: // VSM shadow mipmap pass FrameGraphId vsmMipmapPass(FrameGraph& fg, FrameGraphId input, uint8_t layer, size_t level, - math::float4 clearColor, bool finalize) noexcept; + math::float4 clearColor) noexcept; FrameGraphId gaussianBlurPass(FrameGraph& fg, FrameGraphId input, diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index e48b520386..3c512284c0 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -518,9 +518,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // So generate the mipmaps for each layer if (textureRequirements.levels > 1) { for (size_t level = 0; level < textureRequirements.levels - 1; level++) { - const bool finalize = level == textureRequirements.levels - 2; - ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, - vsmClearColor, finalize); + ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, vsmClearColor); } } } diff --git a/filament/src/details/IndirectLight.cpp b/filament/src/details/IndirectLight.cpp index 2822266c72..6d78d56408 100644 --- a/filament/src/details/IndirectLight.cpp +++ b/filament/src/details/IndirectLight.cpp @@ -200,11 +200,13 @@ void FIndirectLight::terminate(FEngine& engine) { } backend::Handle FIndirectLight::getReflectionHwHandle() const noexcept { - return mReflectionsTexture ? mReflectionsTexture->getHwHandle() : backend::Handle {}; + return mReflectionsTexture ? mReflectionsTexture->getHwHandleForSampling() + : backend::Handle{}; } backend::Handle FIndirectLight::getIrradianceHwHandle() const noexcept { - return mIrradianceTexture ? mIrradianceTexture->getHwHandle() : backend::Handle {}; + return mIrradianceTexture ? mIrradianceTexture->getHwHandleForSampling() + : backend::Handle{}; } math::float3 FIndirectLight::getDirectionEstimate(math::float3 const* f) noexcept { diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index a9ae242d4a..812e0dbac4 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -27,12 +27,15 @@ #include "private/filament/EngineEnums.h" +#include #include #include #include +#include #include +#include #include #include #include @@ -176,6 +179,14 @@ void FMaterialInstance::commit(DriverApi& driver) const { if (mUniforms.isDirty()) { driver.updateBufferObject(mUbHandle, mUniforms.toBufferDescriptor(driver), 0); } + if (!mTextureParameters.empty()) { + for (auto const& [binding, p]: mTextureParameters) { + assert_invariant(p.texture); + Handle handle = p.texture->getHwHandleForSampling(); + assert_invariant(handle); + mDescriptorSet.setSampler(binding, handle, p.params); + } + } // Commit descriptors if needed (e.g. when textures are updated,or the first time) mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver); } @@ -205,17 +216,25 @@ void FMaterialInstance::setParameterImpl(std::string_view name, PANIC_LOG("Depth textures can't be sampled with a linear filter " "unless the comparison mode is set to COMPARE_TO_TEXTURE. " "(material: \"%s\", parameter: \"%.*s\")", - getMaterial()->getName().c_str(), name.size(), name.data()); + getMaterial()->getName().c_str(), name.size(), name.data()); } } } #endif - Handle handle{}; - if (UTILS_LIKELY(texture)) { - handle = texture->getHwHandle(); + auto binding = mMaterial->getSamplerBinding(name); + if (texture && texture->canHaveTextureView()) { + mTextureParameters[binding] = { texture, sampler.getSamplerParams() }; + } else { + Handle handle{}; + if (texture) { + handle = texture->getHwHandleForSampling(); + assert_invariant(handle == texture->getHwHandle()); + } else { + mTextureParameters.erase(binding); + } + mDescriptorSet.setSampler(binding, handle, sampler.getSamplerParams()); } - setParameter(name, handle, sampler.getSamplerParams()); } void FMaterialInstance::setMaskThreshold(float threshold) noexcept { diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h index 985555b42e..5e16e2505d 100644 --- a/filament/src/details/MaterialInstance.h +++ b/filament/src/details/MaterialInstance.h @@ -29,11 +29,14 @@ #include +#include #include #include #include +#include + #include #include #include @@ -45,6 +48,7 @@ namespace filament { class FMaterial; +class FTexture; class FMaterialInstance : public MaterialInstance { public: @@ -238,7 +242,13 @@ private: // keep these grouped, they're accessed together in the render-loop FMaterial const* mMaterial = nullptr; + struct TextureParameter { + FTexture const* texture; + backend::SamplerParams params; + }; + backend::Handle mUbHandle; + tsl::robin_map mTextureParameters; mutable filament::DescriptorSet mDescriptorSet; UniformBuffer mUniforms; diff --git a/filament/src/details/RenderTarget.cpp b/filament/src/details/RenderTarget.cpp index dad0af4d63..131420b4ef 100644 --- a/filament/src/details/RenderTarget.cpp +++ b/filament/src/details/RenderTarget.cpp @@ -121,7 +121,8 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build backend::MRT mrt{}; TargetBufferInfo dinfo{}; - auto setAttachment = [this](TargetBufferInfo& info, AttachmentPoint attachmentPoint) { + auto setAttachment = [this, &driver = engine.getDriverApi()] + (TargetBufferInfo& info, AttachmentPoint attachmentPoint) { Attachment const& attachment = mAttachments[(size_t)attachmentPoint]; auto t = downcast(attachment.texture); info.handle = t->getHwHandle(); @@ -131,6 +132,7 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build } else { info.layer = attachment.layer; } + t->updateLodRange(info.level); }; UTILS_NOUNROLL diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index 3734df4b6d..ba1689d65f 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -221,6 +222,7 @@ Texture* Texture::Builder::build(Engine& engine) { FTexture::FTexture(FEngine& engine, const Builder& builder) { FEngine::DriverApi& driver = engine.getDriverApi(); + mDriver = &driver; // this is unfortunately needed for getHwHandleForSampling() mWidth = static_cast(builder->mWidth); mHeight = static_cast(builder->mHeight); mDepth = static_cast(builder->mDepth); @@ -243,11 +245,15 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) { mHandle = driver.importTexture(builder->mImportedId, mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage); } + mHandleForSampling = mHandle; } // frees driver resources, object becomes invalid void FTexture::terminate(FEngine& engine) { FEngine::DriverApi& driver = engine.getDriverApi(); + if (mHandleForSampling != mHandle) { + driver.destroyTexture(mHandleForSampling); + } driver.destroyTexture(mHandle); } @@ -349,6 +355,9 @@ void FTexture::setImage(FEngine& engine, size_t level, engine.getDriverApi().update3DImage(mHandle, uint8_t(level), xoffset, yoffset, zoffset, width, height, depth, std::move(p)); + + // this method shouldn't have been const + const_cast(this)->updateLodRange(level); } // deprecated @@ -415,6 +424,9 @@ void FTexture::setImage(FEngine& engine, size_t level, engine.getDriverApi().queueCommand( make_copyable_function([buffer = std::move(buffer)]() {})); } + + // this method shouldn't been const + const_cast(this)->updateLodRange(level); } void FTexture::setExternalImage(FEngine& engine, void* image) noexcept { @@ -464,6 +476,58 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept { } engine.getDriverApi().generateMipmaps(mHandle); + // this method shouldn't have been const + const_cast(this)->updateLodRange(0, mLevelCount); +} + +bool FTexture::canHaveTextureView() const noexcept { + // TODO: this will eventually include swizzling + return any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1; +} + +void FTexture::updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept { + if (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) { + auto& range = mLodRange; + uint8_t const last = int8_t(baseLevel + levelCount); + if (range.first > baseLevel || range.last < last) { + if (range.empty()) { + range = { baseLevel, last }; + } else { + range.first = std::min(range.first, baseLevel); + range.last = std::max(range.last, last); + } + if (range.first == 0 && range.last == mLevelCount) { + // the whole range lod range is used, we don't need the view anymore + range.first = range.last = 0; + } + // We defer the creation of the texture view to getHwHandleForSampling() because it + // is a common case that by then, the view won't be needed. Creating the first view on a + // texture has a backend cost. + } + } +} + +backend::Handle FTexture::getHwHandleForSampling() const noexcept { + auto const& range = mLodRange; + auto& activeRange = mActiveLodRange; + if (UTILS_UNLIKELY(activeRange.first != range.first || activeRange.last != range.last)) { + activeRange = range; + if (mHandleForSampling != mHandle) { + mDriver->destroyTexture(mHandleForSampling); + } + if (range.empty()) { + mHandleForSampling = mHandle; + } else { + mHandleForSampling = mDriver->createTextureView(mHandle, + range.first, + range.last - range.first); + } + } + return mHandleForSampling; +} + +void FTexture::updateLodRange(uint8_t level) noexcept { + updateLodRange(level, 1); } bool FTexture::isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept { diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index 0e0fe9320e..7c49ea746b 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -19,12 +19,17 @@ #include "downcast.h" +#include +#include #include #include #include +#include +#include + namespace filament { class FEngine; @@ -38,6 +43,7 @@ public: void terminate(FEngine& engine); backend::Handle getHwHandle() const noexcept { return mHandle; } + backend::Handle getHwHandleForSampling() const noexcept; size_t getWidth(size_t level = 0) const noexcept; size_t getHeight(size_t level = 0) const noexcept; @@ -114,10 +120,25 @@ public: static bool validatePixelFormatAndType(backend::TextureFormat internalFormat, backend::PixelDataFormat format, backend::PixelDataType type) noexcept; + bool canHaveTextureView() const noexcept; + void updateLodRange(uint8_t level) noexcept; + private: friend class Texture; - FStream* mStream = nullptr; + struct LodRange { + // 0,0 means lod-range unset (all levels are available) + uint8_t first = 0; // first lod + uint8_t last = 0; // 1 past last lod + bool empty() const noexcept { return first == last; } + }; + void updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept; + backend::Handle mHandle; + mutable backend::Handle mHandleForSampling; + backend::DriverApi* mDriver = nullptr; // this is only needed for getHwHandleForSampling() + LodRange mLodRange{}; + mutable LodRange mActiveLodRange{}; + uint32_t mWidth = 1; uint32_t mHeight = 1; uint32_t mDepth = 1; @@ -125,9 +146,11 @@ private: Sampler mTarget = Sampler::SAMPLER_2D; uint8_t mLevelCount = 1; uint8_t mSampleCount = 1; - Usage mUsage = Usage::DEFAULT; -}; + Usage mUsage = Usage::DEFAULT; + // there is 7 bytes of padding here + FStream* mStream = nullptr; // only needed for streaming textures +}; FILAMENT_DOWNCAST(Texture) diff --git a/filament/src/ds/ColorPassDescriptorSet.cpp b/filament/src/ds/ColorPassDescriptorSet.cpp index 326db28563..f64739df45 100644 --- a/filament/src/ds/ColorPassDescriptorSet.cpp +++ b/filament/src/ds/ColorPassDescriptorSet.cpp @@ -259,7 +259,7 @@ void ColorPassDescriptorSet::prepareFog(FEngine& engine, const CameraInfo& camer // currently they're inferred. Handle fogColorTextureHandle; if (options.skyColor) { - fogColorTextureHandle = downcast(options.skyColor)->getHwHandle(); + fogColorTextureHandle = downcast(options.skyColor)->getHwHandleForSampling(); math::half2 const minMaxMip{ 0.0f, float(options.skyColor->getLevels()) - 1.0f }; s.fogMinMaxMip = packHalf2x16(minMaxMip); s.fogOneOverFarMinusNear = 1.0f / (cameraInfo.zf - cameraInfo.zn); @@ -282,7 +282,8 @@ void ColorPassDescriptorSet::prepareFog(FEngine& engine, const CameraInfo& camer } setSampler(+PerViewBindingPoints::FOG, - fogColorTextureHandle ? fogColorTextureHandle : engine.getDummyCubemap()->getHwHandle(), { + fogColorTextureHandle ? + fogColorTextureHandle : engine.getDummyCubemap()->getHwHandleForSampling(), { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR });