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:
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, ©Region);
|
||||
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, ©Region);
|
||||
|
||||
transitionLayout(&commands, transitionRange, nextLayout);
|
||||
}
|
||||
|
||||
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
auto* const state = mAllocator->handle_cast<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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user