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.
This commit is contained in:
Mathias Agopian
2024-06-28 12:23:58 -07:00
parent 315c8d75bc
commit d2cb53e39d
31 changed files with 722 additions and 490 deletions
@@ -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,
+11 -13
View File
@@ -438,6 +438,13 @@ void MetalDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType targe
th.getId(), stringify(target), levels, samples, width, height, depth, stringify(usage));
}
void MetalDriver::createTextureViewR(Handle<HwTexture> th,
Handle<HwTexture> srch, uint8_t baseLevel, uint8_t levelCount) {
MetalTexture const* src = handle_cast<MetalTexture>(srch);
mContext->textures.insert(construct_handle<MetalTexture>(th, *mContext,
src, baseLevel, levelCount));
}
void MetalDriver::importTextureR(Handle<HwTexture> 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<HwRenderTarget> rth,
auto colorTexture = handle_cast<MetalTexture>(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<HwRenderTarget> rth,
auto depthTexture = handle_cast<MetalTexture>(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<HwRenderTarget> rth,
auto stencilTexture = handle_cast<MetalTexture>(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<HwTexture> MetalDriver::createTextureSwizzledS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::createTextureViewS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::importTextureS() noexcept {
return alloc_handle<MetalTexture>();
}
@@ -1084,14 +1092,6 @@ void MetalDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t ind
vertexBuffer->buffers[index] = bufferObject->getBuffer();
}
void MetalDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
auto tex = handle_cast<MetalTexture>(th);
tex->setLodRange(minLevel, maxLevel);
DEBUG_LOG("setMinMaxLevels(th = %d, minLevel = %d, maxLevel = %d)\n", th.getId(), minLevel,
maxLevel);
}
void MetalDriver::update3DImage(Handle<HwTexture> 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",
+8 -20
View File
@@ -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<MTLTexture> outside of Filament.
MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
@@ -239,28 +242,17 @@ public:
~MetalTexture();
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle and
// LOD clamping.
id<MTLTexture> getMtlTextureForRead() noexcept;
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle.
id<MTLTexture> getMtlTextureForRead() const noexcept;
// Returns the id<MTLTexture> for attaching to a render pass.
id<MTLTexture> getMtlTextureForWrite() noexcept {
id<MTLTexture> 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<MTLTexture> swizzledTextureView = nil;
id<MTLTexture> lodTextureView = nil;
uint16_t minLod = std::numeric_limits<uint16_t>::max();
uint16_t maxLod = 0;
bool terminated = false;
};
+13 -43
View File
@@ -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<MTLTexture> 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<MTLTexture> MetalTexture::getMtlTextureForRead() noexcept {
// TODO: get LODs to work correctly
id<MTLTexture> 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<MTLTexture> 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<MTLTexture> 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 <MTLBlitCommandEncoder> 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
-3
View File
@@ -250,9 +250,6 @@ void NoopDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t inde
Handle<HwBufferObject> boh) {
}
void NoopDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
}
void NoopDriver::update3DImage(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
@@ -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<T, Sampler> ||
std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
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<typename T>
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<GLTextureRef*>(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<decltype(arg)>;
if constexpr (std::is_same_v<T, Buffer>) {
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);
+18 -3
View File
@@ -29,6 +29,8 @@
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <math/half.h>
#include <variant>
#include <stddef.h>
@@ -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<GLTextureRef> 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<GLTextureRef> 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<typename T>
static void updateTextureLod(OpenGLContext& gl,
HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept;
utils::FixedCapacityVector<Descriptor> descriptors; // 16
utils::bitset64 dynamicBuffers; // 8
DescriptorSetLayoutHandle dslh; // 4
+9 -1
View File
@@ -21,12 +21,20 @@
#include "gl_headers.h"
#include <backend/Handle.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <stdint.h>
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<GLTextureRef> ref;
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
};
+79 -109
View File
@@ -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 <type_traits>
#include <utils/BitmaskEnum.h>
#include <utils/FixedCapacityVector.h>
#include <utils/CString.h>
@@ -512,6 +514,10 @@ Handle<HwTexture> OpenGLDriver::createTextureSwizzledS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::importTextureS() noexcept {
return initHandle<GLTexture>();
}
@@ -901,6 +907,52 @@ void OpenGLDriver::createTextureSwizzledR(Handle<HwTexture> th,
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
Handle<HwTexture> srch, uint8_t baseLevel, uint8_t levelCount) {
DEBUG_MARKER()
GLTexture const* const src = handle_cast<GLTexture const*>(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<GLTextureRef>();
}
GLTexture* t = construct<GLTexture>(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<GLTextureRef*>(t->ref);
assert_invariant(ref);
ref->count++;
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::importTextureR(Handle<HwTexture> 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<HwTexture> th) {
if (th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture*>(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<GLTextureRef*>(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<HwTexture> is always destroyed. For extra precaution we also
// check that the GLTexture has a trivial destructor.
static_assert(std::is_trivially_destructible_v<GLTexture>);
}
} else {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
@@ -2361,28 +2424,6 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescript
// TODO: goes away
}
void OpenGLDriver::setMinMaxLevels(Handle<HwTexture> 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<GLTexture*>(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<HwTexture> 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<HwTexture> th) {
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
t->gl.baseLevel = 0;
t->gl.maxLevel = static_cast<int8_t>(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();
@@ -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<void()>& fn) noexcept;
@@ -17,6 +17,7 @@
#include "OpenGLProgram.h"
#include "GLUtils.h"
#include "GLTexture.h"
#include "OpenGLDriver.h"
#include "ShaderCompilerService.h"
@@ -24,8 +25,9 @@
#include <backend/Program.h>
#include <backend/Handle.h>
#include <utils/debug.h>
#include <utils/BitmaskEnum.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
+26 -15
View File
@@ -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<VulkanTimestamps>(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<HwTexture> th, SamplerType target, uint
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
auto vktexture = mResourceAllocator.construct<VulkanTexture>(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<HwTexture> th, SamplerType targ
TextureSwizzle swizzleArray[] = {r, g, b, a};
const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
auto vktexture = mResourceAllocator.construct<VulkanTexture>(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<HwTexture> th,
Handle<HwTexture> srch, uint8_t baseLevel, uint8_t levelCount) {
VulkanTexture const* src = mResourceAllocator.handle_cast<VulkanTexture const*>(srch);
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th,
mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands,
&mResourceAllocator, src, baseLevel, levelCount, mStagePool);
mResourceManager.acquire(vktexture);
}
@@ -660,7 +671,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
assert_invariant(tmin.x >= width && tmin.y >= height);
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(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<HwSwapChain> sch, void* nativeWindow,
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(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<HwSwapChain> sch, uint32_t wi
}
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(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<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
@@ -1128,11 +1145,6 @@ void VulkanDriver::resetBufferObject(Handle<HwBufferObject> boh) {
// This is only useful if updateBufferObjectUnsynchronized() is implemented unsynchronizedly.
}
void VulkanDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
auto texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
texture->setPrimaryRange(minLevel, maxLevel);
}
void VulkanDriver::update3DImage(Handle<HwTexture> 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<HwTexture> th) {
srcw = dstw;
srch = dsth;
} while ((srcw > 1 || srch > 1) && level < t->levels);
t->setPrimaryRange(0, t->levels - 1);
}
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
@@ -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);
+3 -1
View File
@@ -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);
@@ -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<VulkanTexture>(device, mAllocator, mCommands, color,
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
mColors.push_back(std::make_unique<VulkanTexture>(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<VulkanTexture>(device, mAllocator, mCommands, bundle.depth,
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
mDepth = std::make_unique<VulkanTexture>(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;
@@ -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;
+135 -81
View File
@@ -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<VulkanTextureState*>(mState);
return state;
}
VulkanTextureState const* VulkanTexture::getSharedState() const {
VulkanTextureState const* state = mAllocator->handle_cast<VulkanTextureState const*>(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<VulkanTextureState>(
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<VulkanTextureState*>(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<VulkanTextureState>(
device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), swizzle))
{
auto* const state = handleAllocator->handle_cast<VulkanTextureState*>(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<int>(usage) << ", "
@@ -175,13 +204,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(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<VulkanTextureState*>(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<VulkanTextureState*>(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<VulkanTextureState>(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<VulkanTextureState*>(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, &copyRegion);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, &copyRegion);
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<VulkanTextureState*>(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<VulkanTextureState*>(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<VulkanTextureState*>(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<VulkanTextureState*>(mState);
return filament::backend::getImageAspect(state->mVkFormat);
}
void VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
@@ -425,6 +476,7 @@ void VulkanTexture::transitionLayout(
VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
const VkImageSubresourceRange& range,
VulkanLayout newLayout) {
auto* const state = mAllocator->handle_cast<VulkanTextureState*>(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<VulkanTextureState*>(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<VulkanTextureState*>(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)
+141 -101
View File
@@ -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<VulkanCmdFence> 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<ImageViewKey>;
// 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<VulkanTexture> 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<uint32_t, VulkanLayout> 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<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
@@ -173,6 +80,139 @@ private:
std::shared_ptr<VulkanCmdFence> 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<VulkanCmdFence> 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<VulkanTextureState> 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
+24 -13
View File
@@ -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<HwTexture> 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<HwRenderTarget> 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);
+18 -7
View File
@@ -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);
}
+1 -1
View File
@@ -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**
+26 -41
View File
@@ -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<FrameGraphTexture> 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<FrameGraphTexture> 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<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input, uint8_t layer, size_t level,
math::float4 clearColor, bool finalize) noexcept {
math::float4 clearColor) noexcept {
struct VsmMipData {
FrameGraphId<FrameGraphTexture> in;
@@ -3284,7 +3274,7 @@ FrameGraphId<FrameGraphTexture> 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<FrameGraphTexture> 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<FrameGraphTexture> 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;
+1 -1
View File
@@ -296,7 +296,7 @@ public:
// VSM shadow mipmap pass
FrameGraphId<FrameGraphTexture> vsmMipmapPass(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input, uint8_t layer, size_t level,
math::float4 clearColor, bool finalize) noexcept;
math::float4 clearColor) noexcept;
FrameGraphId<FrameGraphTexture> gaussianBlurPass(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input,
+1 -3
View File
@@ -518,9 +518,7 @@ FrameGraphId<FrameGraphTexture> 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);
}
}
}
+4 -2
View File
@@ -200,11 +200,13 @@ void FIndirectLight::terminate(FEngine& engine) {
}
backend::Handle<backend::HwTexture> FIndirectLight::getReflectionHwHandle() const noexcept {
return mReflectionsTexture ? mReflectionsTexture->getHwHandle() : backend::Handle<backend::HwTexture> {};
return mReflectionsTexture ? mReflectionsTexture->getHwHandleForSampling()
: backend::Handle<backend::HwTexture>{};
}
backend::Handle<backend::HwTexture> FIndirectLight::getIrradianceHwHandle() const noexcept {
return mIrradianceTexture ? mIrradianceTexture->getHwHandle() : backend::Handle<backend::HwTexture> {};
return mIrradianceTexture ? mIrradianceTexture->getHwHandleForSampling()
: backend::Handle<backend::HwTexture>{};
}
math::float3 FIndirectLight::getDirectionEstimate(math::float3 const* f) noexcept {
+24 -5
View File
@@ -27,12 +27,15 @@
#include "private/filament/EngineEnums.h"
#include <filament/MaterialEnums.h>
#include <filament/TextureSampler.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/BitmaskEnum.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
@@ -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<HwTexture> 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<HwTexture> handle{};
if (UTILS_LIKELY(texture)) {
handle = texture->getHwHandle();
auto binding = mMaterial->getSamplerBinding(name);
if (texture && texture->canHaveTextureView()) {
mTextureParameters[binding] = { texture, sampler.getSamplerParams() };
} else {
Handle<HwTexture> 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 {
+10
View File
@@ -29,11 +29,14 @@
#include <filament/MaterialInstance.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/BitmaskEnum.h>
#include <utils/CString.h>
#include <tsl/robin_map.h>
#include <algorithm>
#include <limits>
#include <mutex>
@@ -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<backend::HwBufferObject> mUbHandle;
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable filament::DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
+3 -1
View File
@@ -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
+64
View File
@@ -39,6 +39,7 @@
#include <utils/Allocator.h>
#include <utils/algorithm.h>
#include <utils/BitmaskEnum.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
@@ -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<uint32_t>(builder->mWidth);
mHeight = static_cast<uint32_t>(builder->mHeight);
mDepth = static_cast<uint32_t>(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<FTexture*>(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<FTexture*>(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<FTexture*>(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<backend::HwTexture> 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 {
+26 -3
View File
@@ -19,12 +19,17 @@
#include "downcast.h"
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <filament/Texture.h>
#include <utils/compiler.h>
#include <stddef.h>
#include <stdint.h>
namespace filament {
class FEngine;
@@ -38,6 +43,7 @@ public:
void terminate(FEngine& engine);
backend::Handle<backend::HwTexture> getHwHandle() const noexcept { return mHandle; }
backend::Handle<backend::HwTexture> 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<backend::HwTexture> mHandle;
mutable backend::Handle<backend::HwTexture> 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)
+3 -2
View File
@@ -259,7 +259,7 @@ void ColorPassDescriptorSet::prepareFog(FEngine& engine, const CameraInfo& camer
// currently they're inferred.
Handle<HwTexture> 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
});