From 42812ad52133b162f82d6b735a4a438dbf1363c9 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sat, 24 Feb 2024 15:45:43 -0400 Subject: [PATCH] Add texture views. Fixes #2022. Add love.graphics.newTextureView(basetexture, viewsettings). Requires GLSL4 support. Add 'viewformats' boolean setting to texture setting tables. Add Texture:hasViewFormats. The viewsettings table has the following fields: format = nil, -- set this to a pixel format to override the base format. It must have the same number of bytes per pixel as the original, and the 'viewformats' setting on the original texture must be true for this to work. Cannot be used for compressed or depth/stencil textures. type = nil, -- set this to a texture type to override the base texture type. 2d base textures can make [2d, array] views. [array, cube] base textures can make [2d, array, cube] views. mipmapstart = nil, -- set to a number to use a less detailed mipmap level as a base. mipmapcount = nil, -- set to a number to override the number of mipmaps in the texture view. layerstart = nil, -- set to a number to use a specific layer or cube face as a base. layers = nil, -- set to a number to override the number of layers in an array texture view. --- src/modules/graphics/Graphics.cpp | 24 +- src/modules/graphics/Graphics.h | 1 + src/modules/graphics/Shader.cpp | 2 +- src/modules/graphics/Texture.cpp | 279 +++++++++++++++++------ src/modules/graphics/Texture.h | 52 ++++- src/modules/graphics/metal/Graphics.h | 1 + src/modules/graphics/metal/Graphics.mm | 5 + src/modules/graphics/metal/Texture.h | 9 +- src/modules/graphics/metal/Texture.mm | 47 +++- src/modules/graphics/opengl/Graphics.cpp | 5 + src/modules/graphics/opengl/Graphics.h | 1 + src/modules/graphics/opengl/OpenGL.cpp | 2 + src/modules/graphics/opengl/Texture.cpp | 57 +++-- src/modules/graphics/opengl/Texture.h | 2 + src/modules/graphics/vulkan/Graphics.cpp | 5 + src/modules/graphics/vulkan/Graphics.h | 1 + src/modules/graphics/vulkan/Texture.cpp | 222 ++++++++++-------- src/modules/graphics/vulkan/Texture.h | 12 +- src/modules/graphics/wrap_Graphics.cpp | 69 ++++++ src/modules/graphics/wrap_Texture.cpp | 8 + 20 files changed, 588 insertions(+), 216 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 64ddfa62e..daf1cdf32 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -1203,15 +1203,16 @@ bool Graphics::isRenderTargetActive() const bool Graphics::isRenderTargetActive(Texture *texture) const { + Texture *roottexture = texture->getRootViewInfo().texture; const auto &rts = states.back().renderTargets; for (const auto &rt : rts.colors) { - if (rt.texture.get() == texture) + if (rt.texture.get() && rt.texture->getRootViewInfo().texture == roottexture) return true; } - if (rts.depthStencil.texture.get() == texture) + if (rts.depthStencil.texture.get() && rts.depthStencil.texture->getRootViewInfo().texture == roottexture) return true; return false; @@ -1219,16 +1220,27 @@ bool Graphics::isRenderTargetActive(Texture *texture) const bool Graphics::isRenderTargetActive(Texture *texture, int slice) const { + const auto &rootinfo = texture->getRootViewInfo(); + slice += rootinfo.startLayer; + const auto &rts = states.back().renderTargets; for (const auto &rt : rts.colors) { - if (rt.texture.get() == texture && rt.slice == slice) - return true; + if (rt.texture.get()) + { + const auto &info = rt.texture->getRootViewInfo(); + if (rootinfo.texture == info.texture && rt.slice + info.startLayer == slice) + return true; + } } - if (rts.depthStencil.texture.get() == texture && rts.depthStencil.slice == slice) - return true; + if (rts.depthStencil.texture.get()) + { + const auto &info = rts.depthStencil.texture->getRootViewInfo(); + if (rootinfo.texture == info.texture && rts.depthStencil.slice + info.startLayer == slice) + return true; + } return false; } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index cef0d4e72..a09985a81 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -455,6 +455,7 @@ public: virtual ~Graphics(); virtual Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) = 0; + virtual Texture *newTextureView(Texture *base, const Texture::ViewSettings &viewsettings) = 0; Quad *newQuad(Quad::Viewport v, double sw, double sh); Font *newFont(love::font::Rasterizer *data); diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index 618219d1f..cfe9e1a6c 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -1141,7 +1141,7 @@ bool Shader::validateTexture(const UniformInfo *info, Texture *tex, bool interna else throw love::Exception("Texture must be created with the computewrite flag set to true in order to be used with a storage texture (image2D etc) shader uniform variable."); } - else if (isstoragetex && info->storageTextureFormat != getLinearPixelFormat(tex->getPixelFormat())) + else if (isstoragetex && info->storageTextureFormat != tex->getPixelFormat()) { if (internalUpdate) return false; diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index c35d2073f..fb989328d 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -166,6 +166,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) , format(settings.format) , renderTarget(settings.renderTarget) , computeWrite(settings.computeWrite) + , viewFormats(settings.viewFormats) , readable(true) , mipmapsMode(settings.mipmaps) , width(settings.width) @@ -179,6 +180,8 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) , samplerState() , graphicsMemorySize(0) , debugName(settings.debugName) + , rootView({this, 0, 0}) + , parentView({this, 0, 0}) { const auto &caps = gfx->getCapabilities(); int requestedMipmapCount = settings.mipmapCount; @@ -284,31 +287,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) if (isCompressed() && renderTarget) throw love::Exception("Compressed textures cannot be render targets."); - uint32 usage = PIXELFORMATUSAGEFLAGS_NONE; - if (renderTarget) - usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; - if (readable) - usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; - if (computeWrite) - usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; - - if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage)) - { - const char *fstr = "unknown"; - love::getConstant(format, fstr); - - const char *readablestr = ""; - if (readable != !isPixelFormatDepthStencil(format)) - readablestr = readable ? " readable" : " non-readable"; - - const char *rtstr = ""; - if (computeWrite) - rtstr = " as a compute shader-writable texture"; - else if (renderTarget) - rtstr = " as a render target"; - - throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr); - } + validatePixelFormat(gfx); if (!caps.textureTypes[texType]) { @@ -330,10 +309,149 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) ++textureCount; } +Texture::Texture(Graphics *gfx, Texture *base, const ViewSettings &viewsettings) + : texType(viewsettings.type.get(base->getTextureType())) + , format(viewsettings.format.get(base->getPixelFormat())) + , renderTarget(base->renderTarget) + , computeWrite(base->computeWrite) + , viewFormats(base->viewFormats) + , readable(base->readable) + , mipmapsMode(base->mipmapsMode) + , width(1) + , height(1) + , depth(1) + , layers(1) + , mipmapCount(1) + , pixelWidth(1) + , pixelHeight(1) + , requestedMSAA(base->requestedMSAA) + , samplerState(base->samplerState) + , quad(base->quad) + , graphicsMemorySize(0) + , debugName(viewsettings.debugName) + , rootView({base->rootView.texture, 0, 0}) + , parentView({base, viewsettings.mipmapStart.get(0), viewsettings.layerStart.get(0)}) +{ + width = base->getHeight(parentView.startMipmap); + height = base->getHeight(parentView.startMipmap); + + if (texType == TEXTURE_VOLUME) + depth = base->getDepth(parentView.startMipmap); + + if (texType == TEXTURE_2D_ARRAY) + { + int baselayers = base->getTextureType() == TEXTURE_CUBE ? 6 : base->getLayerCount(); + layers = viewsettings.layerCount.get(baselayers - parentView.startLayer); + } + + mipmapCount = viewsettings.mipmapCount.get(base->getMipmapCount() - parentView.startMipmap); + + pixelWidth = base->getPixelWidth(parentView.startMipmap); + pixelHeight = base->getPixelHeight(parentView.startMipmap); + + if (parentView.startMipmap < 0) + throw love::Exception("Invalid mipmap start value for texture view (out of range)."); + + if (mipmapCount < 0 || parentView.startMipmap + mipmapCount > base->getMipmapCount()) + throw love::Exception("Invalid mipmap start or count value for texture view (out of range)."); + + if (parentView.startLayer < 0) + throw love::Exception("Invalid layer start value for texture view (out of range)."); + + int baseLayerCount = base->getTextureType() == TEXTURE_CUBE ? 6 : base->getLayerCount(); + if (layers < 0 || parentView.startLayer + layers > baseLayerCount) + throw love::Exception("Invalid layer start or count value for texture view (out of range)."); + + if (texType == TEXTURE_CUBE && parentView.startLayer + 6 > baseLayerCount) + throw love::Exception("Cube texture view cannot fit in the base texture's layers with the given start layer."); + + ViewInfo nextView = { this, 0, 0 }; + while (nextView.texture != rootView.texture) + { + nextView = nextView.texture->parentView; + rootView.startMipmap += nextView.startMipmap; + rootView.startLayer += nextView.startLayer; + } + + const auto &caps = gfx->getCapabilities(); + if (!caps.features[Graphics::FEATURE_GLSL4]) + throw love::Exception("Texture views are not supported on this system (GLSL 4 support is necessary.)"); + + validatePixelFormat(gfx); + + if (!caps.textureTypes[texType]) + { + const char *textypestr = "unknown"; + Texture::getConstant(texType, textypestr); + throw love::Exception("%s textures are not supported on this system.", textypestr); + } + + if (!readable) + throw love::Exception("Texture views are not supported for non-readable textures."); + + if (base->getTextureType() == TEXTURE_2D) + { + if (texType != TEXTURE_2D && texType != TEXTURE_2D_ARRAY) + throw love::Exception("Texture views created from a 2D texture must use the 2d or array texture type."); + } + else if (base->getTextureType() == TEXTURE_2D_ARRAY || base->getTextureType() == TEXTURE_CUBE) + { + if (texType != TEXTURE_2D && texType != TEXTURE_2D_ARRAY && texType != TEXTURE_CUBE) + throw love::Exception("Texture views created from an array or cube texture must use the 2d, array, or cube texture type."); + } + else if (base->getTextureType() == TEXTURE_VOLUME) + { + if (texType != TEXTURE_VOLUME) + throw love::Exception("Texture views created from a volume texture must use the volume texture type."); + } + else + { + throw love::Exception("Unknown texture type."); + } + + auto baseformat = base->getPixelFormat(); + + if (format != baseformat) + { + if (isPixelFormatCompressed(baseformat) || isPixelFormatCompressed(format)) + throw love::Exception("Compressed textures cannot use a different pixel format in a texture view."); + + if (isPixelFormatColor(baseformat) != isPixelFormatColor(format)) + throw love::Exception("Color-format textures cannot use a depth/stencil pixel format and vice versa, in a texture view."); + + // TODO: depth[24|32f]_stencil8 -> stencil8 can work. + if (isPixelFormatDepthStencil(baseformat)) + throw love::Exception("Using a different pixel format in a texture view is not currently supported for depth or stencil formats."); + + if (!viewFormats) + throw love::Exception("Using a different pixel format in a texture view requires the original texture to be created with the 'viewformats' setting set to true."); + + size_t bytes = getPixelFormatBlockSize(format); + size_t basebytes = getPixelFormatBlockSize(baseformat); + + if (bytes != basebytes) + throw love::Exception("Texture views must have the same bits per pixel as the original texture."); + } + + const char *miperr = nullptr; + if (mipmapsMode == MIPMAPS_AUTO && !supportsGenerateMipmaps(miperr)) + mipmapsMode = MIPMAPS_MANUAL; + + rootView.texture->retain(); + parentView.texture->retain(); +} + Texture::~Texture() { - --textureCount; setGraphicsMemorySize(0); + + if (this == rootView.texture) + --textureCount; + + if (rootView.texture != this && rootView.texture != nullptr) + rootView.texture->release(); + if (parentView.texture != this && parentView.texture != nullptr) + parentView.texture->release(); } void Texture::setGraphicsMemorySize(int64 bytes) @@ -352,18 +470,18 @@ void Texture::draw(Graphics *gfx, const Matrix4 &m) void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform) { - if (!readable) - throw love::Exception("Textures with non-readable formats cannot be drawn."); - - if (renderTarget && gfx->isRenderTargetActive(this)) - throw love::Exception("Cannot render a Texture to itself."); - if (texType == TEXTURE_2D_ARRAY) { drawLayer(gfx, q->getLayer(), q, localTransform); return; } + if (!readable) + throw love::Exception("Textures with non-readable formats cannot be drawn."); + + if (renderTarget && gfx->isRenderTargetActive(this)) + throw love::Exception("Cannot render a Texture to itself."); + const Matrix4 &tm = gfx->getTransform(); bool is2D = tm.isAffine2DTransform(); @@ -582,36 +700,6 @@ void Texture::generateMipmaps() generateMipmapsInternal(); } -TextureType Texture::getTextureType() const -{ - return texType; -} - -PixelFormat Texture::getPixelFormat() const -{ - return format; -} - -Texture::MipmapsMode Texture::getMipmapsMode() const -{ - return mipmapsMode; -} - -bool Texture::isRenderTarget() const -{ - return renderTarget; -} - -bool Texture::isComputeWritable() const -{ - return computeWrite; -} - -bool Texture::isReadable() const -{ - return readable; -} - bool Texture::isCompressed() const { return isPixelFormatCompressed(format); @@ -685,28 +773,39 @@ int Texture::getRequestedMSAA() const return requestedMSAA; } -void Texture::setSamplerState(const SamplerState &s) +const SamplerState &Texture::getSamplerState() const +{ + return samplerState; +} + +SamplerState Texture::validateSamplerState(SamplerState s) const { if (!readable) - return; + return s; if (s.depthSampleMode.hasValue && !isPixelFormatDepth(format)) throw love::Exception("Only depth textures can have a depth sample compare mode."); - Graphics::flushBatchedDrawsGlobal(); - - samplerState = s; - - if (samplerState.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && getMipmapCount() == 1) - samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + if (s.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && getMipmapCount() == 1) + s.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; if (texType == TEXTURE_CUBE) - samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP; -} + s.wrapU = s.wrapV = s.wrapW = SamplerState::WRAP_CLAMP; -const SamplerState &Texture::getSamplerState() const -{ - return samplerState; + if (s.minFilter == SamplerState::FILTER_LINEAR || s.magFilter == SamplerState::FILTER_LINEAR || s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + { + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (!gfx->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_LINEAR)) + { + s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST; + if (s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + s.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST; + } + } + + Graphics::flushBatchedDrawsGlobal(); + + return s; } Quad *Texture::getQuad() const @@ -781,6 +880,35 @@ bool Texture::validateDimensions(bool throwException) const return success; } +void Texture::validatePixelFormat(Graphics *gfx) const +{ + uint32 usage = PIXELFORMATUSAGEFLAGS_NONE; + if (renderTarget) + usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; + if (readable) + usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + if (computeWrite) + usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + + if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage)) + { + const char *fstr = "unknown"; + love::getConstant(format, fstr); + + const char *readablestr = ""; + if (readable != !isPixelFormatDepthStencil(format)) + readablestr = readable ? " readable" : " non-readable"; + + const char *rtstr = ""; + if (computeWrite) + rtstr = " as a compute shader-writable texture"; + else if (renderTarget) + rtstr = " as a render target"; + + throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr); + } +} + Texture::Slices::Slices(TextureType textype) : textureType(textype) { @@ -971,6 +1099,7 @@ static StringMap::Entry setting { "msaa", Texture::SETTING_MSAA }, { "canvas", Texture::SETTING_RENDER_TARGET }, { "computewrite", Texture::SETTING_COMPUTE_WRITE }, + { "viewformats", Texture::SETTING_VIEW_FORMATS }, { "readable", Texture::SETTING_READABLE }, { "debugname", Texture::SETTING_DEBUGNAME }, }; diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index 68dc52eb6..099442d50 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -174,6 +174,7 @@ public: SETTING_MSAA, SETTING_RENDER_TARGET, SETTING_COMPUTE_WRITE, + SETTING_VIEW_FORMATS, SETTING_READABLE, SETTING_DEBUGNAME, SETTING_MAX_ENUM @@ -194,10 +195,22 @@ public: int msaa = 1; bool renderTarget = false; bool computeWrite = false; + bool viewFormats = false; OptionalBool readable; std::string debugName; }; + struct ViewSettings + { + Optional format; + Optional type; + OptionalInt mipmapStart; + OptionalInt mipmapCount; + OptionalInt layerStart; + OptionalInt layerCount; + std::string debugName; + }; + struct Slices { public: @@ -228,10 +241,14 @@ public: }; // Slices - static int64 totalGraphicsMemory; + struct ViewInfo + { + Texture *texture; + int startMipmap; + int startLayer; + }; - Texture(Graphics *gfx, const Settings &settings, const Slices *slices); - virtual ~Texture(); + static int64 totalGraphicsMemory; // Drawable. void draw(Graphics *gfx, const Matrix4 &m) override; @@ -255,13 +272,14 @@ public: virtual ptrdiff_t getRenderTargetHandle() const = 0; virtual ptrdiff_t getSamplerHandle() const = 0; - TextureType getTextureType() const; - PixelFormat getPixelFormat() const; - MipmapsMode getMipmapsMode() const; + TextureType getTextureType() const { return texType; } + PixelFormat getPixelFormat() const { return format; } + MipmapsMode getMipmapsMode() const { return mipmapsMode; } - bool isRenderTarget() const; - bool isComputeWritable() const; - bool isReadable() const; + bool isRenderTarget() const { return renderTarget; } + bool isComputeWritable() const { return computeWrite; } + bool isReadable() const { return readable; } + bool hasViewFormats() const { return viewFormats; } bool isCompressed() const; bool isFormatLinear() const; @@ -285,11 +303,14 @@ public: int getRequestedMSAA() const; virtual int getMSAA() const = 0; - virtual void setSamplerState(const SamplerState &s); + virtual void setSamplerState(const SamplerState &s) = 0; const SamplerState &getSamplerState() const; Quad *getQuad() const; + const ViewInfo &getRootViewInfo() const { return rootView; } + const ViewInfo &getParentViewInfo() const { return parentView; } + const std::string &getDebugName() const { return debugName; } static int getTotalMipmapCount(int w, int h); @@ -310,6 +331,10 @@ public: protected: + Texture(Graphics *gfx, const Settings &settings, const Slices *slices); + Texture(Graphics *gfx, Texture *base, const ViewSettings &viewsettings); + virtual ~Texture(); + void setGraphicsMemorySize(int64 size); void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y); @@ -318,13 +343,17 @@ protected: bool supportsGenerateMipmaps(const char *&outReason) const; virtual void generateMipmapsInternal() = 0; + SamplerState validateSamplerState(SamplerState s) const; + bool validateDimensions(bool throwException) const; + void validatePixelFormat(Graphics *gfx) const; TextureType texType; PixelFormat format; bool renderTarget; bool computeWrite; + bool viewFormats; bool readable; MipmapsMode mipmapsMode; @@ -349,6 +378,9 @@ protected: std::string debugName; + ViewInfo rootView; + ViewInfo parentView; + }; // Texture } // graphics diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index 95721632b..4a7ebf219 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -61,6 +61,7 @@ public: virtual ~Graphics(); love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override; + love::graphics::Texture *newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) override; love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) override; Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 99e0b5ca4..e4e9dc2af 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -433,6 +433,11 @@ love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, return new Texture(this, device, settings, data); } +love::graphics::Texture *Graphics::newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) +{ + return new Texture(this, device, base, viewsettings); +} + love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) { return new ShaderStage(this, stage, source, gles, cachekey); diff --git a/src/modules/graphics/metal/Texture.h b/src/modules/graphics/metal/Texture.h index 015c779ee..01c6e57ec 100644 --- a/src/modules/graphics/metal/Texture.h +++ b/src/modules/graphics/metal/Texture.h @@ -38,6 +38,7 @@ class Texture final : public love::graphics::Texture public: Texture(love::graphics::Graphics *gfx, id device, const Settings &settings, const Slices *data); + Texture(love::graphics::Graphics *gfx, id device, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings); virtual ~Texture(); void copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) override; @@ -58,11 +59,11 @@ private: void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) override; void generateMipmapsInternal() override; - id texture; - id msaaTexture; - id sampler; + id texture = nil; + id msaaTexture = nil; + id sampler = nil; - int actualMSAASamples; + int actualMSAASamples = 1; }; // Texture diff --git a/src/modules/graphics/metal/Texture.mm b/src/modules/graphics/metal/Texture.mm index 065254918..b5c23c8f3 100644 --- a/src/modules/graphics/metal/Texture.mm +++ b/src/modules/graphics/metal/Texture.mm @@ -43,10 +43,6 @@ static MTLTextureType getMTLTextureType(TextureType type, int msaa) Texture::Texture(love::graphics::Graphics *gfxbase, id device, const Settings &settings, const Slices *data) : love::graphics::Texture(gfxbase, settings, data) - , texture(nil) - , msaaTexture(nil) - , sampler(nil) - , actualMSAASamples(1) { @autoreleasepool { auto gfx = (Graphics *) gfxbase; @@ -80,6 +76,8 @@ Texture::Texture(love::graphics::Graphics *gfxbase, id device, const desc.usage |= MTLTextureUsageRenderTarget; if (computeWrite) desc.usage |= MTLTextureUsageShaderWrite; + if (viewFormats) + desc.usage |= MTLTextureUsagePixelFormatView; texture = [device newTextureWithDescriptor:desc]; @@ -212,6 +210,41 @@ Texture::Texture(love::graphics::Graphics *gfxbase, id device, const setSamplerState(samplerState); }} +Texture::Texture(love::graphics::Graphics *gfx, id device, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) + : love::graphics::Texture(gfx, base, viewsettings) +{ + id basetex = ((Texture *) base)->texture; + auto formatdesc = Metal::convertPixelFormat(device, format); + int slices = texType == TEXTURE_CUBE ? 6 : getLayerCount(); + + if (formatdesc.swizzled) + { + if (@available(macOS 10.15, iOS 13, *)) + { + texture = [basetex newTextureViewWithPixelFormat:formatdesc.format + textureType:getMTLTextureType(texType, 1) + levels:NSMakeRange(parentView.startMipmap, mipmapCount) + slices:NSMakeRange(parentView.startLayer, slices) + swizzle:formatdesc.swizzle]; + } + } + else + { + texture = [basetex newTextureViewWithPixelFormat:formatdesc.format + textureType:getMTLTextureType(texType, 1) + levels:NSMakeRange(parentView.startMipmap, mipmapCount) + slices:NSMakeRange(parentView.startLayer, slices)]; + } + + if (texture == nil) + throw love::Exception("Could not create Metal texture view."); + + if (!debugName.empty()) + texture.label = @(debugName.c_str()); + + setSamplerState(samplerState); +} + Texture::~Texture() { @autoreleasepool { texture = nil; @@ -340,10 +373,8 @@ void Texture::setSamplerState(const SamplerState &s) if (s.depthSampleMode.hasValue && !Graphics::getInstance()->isDepthCompareSamplerSupported()) throw love::Exception("Depth comparison sampling in shaders is not supported on this system."); - // Base class does common validation and assigns samplerState. - love::graphics::Texture::setSamplerState(s); - - sampler = Graphics::getInstance()->getCachedSampler(s); + samplerState = validateSamplerState(s); + sampler = Graphics::getInstance()->getCachedSampler(samplerState); }} } // metal diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 2fd7f6511..58b4d5312 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -158,6 +158,11 @@ love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, return new Texture(this, settings, data); } +love::graphics::Texture *Graphics::newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) +{ + return new Texture(this, base, viewsettings); +} + love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) { return new ShaderStage(this, stage, source, gles, cachekey); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index c1e9e911b..7736731f8 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -57,6 +57,7 @@ public: virtual ~Graphics(); love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override; + love::graphics::Texture *newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) override; love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) override; Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override; diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 0254f184a..533c2755f 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -2506,6 +2506,8 @@ const char *OpenGL::debugSeverityString(GLenum severity) return "medium"; case GL_DEBUG_SEVERITY_LOW: return "low"; + case GL_DEBUG_SEVERITY_NOTIFICATION: + return "notification"; default: return "unknown"; } diff --git a/src/modules/graphics/opengl/Texture.cpp b/src/modules/graphics/opengl/Texture.cpp index 1aa7e6787..49f377f03 100644 --- a/src/modules/graphics/opengl/Texture.cpp +++ b/src/modules/graphics/opengl/Texture.cpp @@ -244,6 +244,25 @@ Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const slices.clear(); } +Texture::Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) + : love::graphics::Texture(gfx, base, viewsettings) + , slices(viewsettings.type.get(base->getTextureType())) + , fbo(0) + , texture(0) + , renderbuffer(0) + , framebufferStatus(GL_FRAMEBUFFER_COMPLETE) + , textureGLError(GL_NO_ERROR) + , actualSamples(1) +{ + if (!loadVolatile()) + { + if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE) + throw love::Exception("Cannot create texture view (OpenGL framebuffer error: %s)", OpenGL::framebufferStatusString(framebufferStatus)); + if (textureGLError != GL_NO_ERROR) + throw love::Exception("Cannot create texture view (OpenGL error: %s)", OpenGL::errorString(textureGLError)); + } +} + Texture::~Texture() { unloadVolatile(); @@ -254,11 +273,26 @@ void Texture::createTexture() // The base class handles some validation. For example, if ImageData is // given then it must exist for all mip levels, a render target can't use // a compressed format, etc. - glGenTextures(1, &texture); + GLenum gltype = OpenGL::getGLTextureType(texType); + + if (parentView.texture != this) + { + OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false); + Texture *basetex = (Texture *) parentView.texture; + int layers = texType == TEXTURE_CUBE ? 6 : getLayerCount(); + + glTextureView(texture, gltype, basetex->texture, fmt.internalformat, + parentView.startMipmap, getMipmapCount(), + parentView.startLayer, layers); + + gl.bindTextureToUnit(this, 0, false); + setSamplerState(samplerState); + return; + } + gl.bindTextureToUnit(this, 0, false); - GLenum gltype = OpenGL::getGLTextureType(texType); if (renderTarget && GLAD_ANGLE_texture_usage) glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); @@ -371,6 +405,12 @@ bool Texture::loadVolatile() if (texture != 0 || renderbuffer != 0) return true; + if (parentView.texture != this) + { + Texture *basetex = (Texture *) parentView.texture; + basetex->loadVolatile(); + } + OpenGL::TempDebugGroup debuggroup("Texture load"); // NPOT textures don't support mipmapping without full NPOT support. @@ -593,18 +633,7 @@ void Texture::setSamplerState(const SamplerState &s) if (s.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported()) throw love::Exception("Depth comparison sampling in shaders is not supported on this system."); - // Base class does common validation and assigns samplerState. - love::graphics::Texture::setSamplerState(s); - - auto supportedflags = OpenGL::getPixelFormatUsageFlags(getPixelFormat()); - - if ((supportedflags & PIXELFORMATUSAGEFLAGS_LINEAR) == 0) - { - samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST; - - if (samplerState.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) - samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST; - } + samplerState = validateSamplerState(s); // If we only have limited NPOT support then the wrap mode must be CLAMP. if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot)) diff --git a/src/modules/graphics/opengl/Texture.h b/src/modules/graphics/opengl/Texture.h index 65f247026..4e507fb2f 100644 --- a/src/modules/graphics/opengl/Texture.h +++ b/src/modules/graphics/opengl/Texture.h @@ -39,6 +39,7 @@ class Texture final : public love::graphics::Texture, public Volatile public: Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data); + Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings); virtual ~Texture(); @@ -61,6 +62,7 @@ public: void readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest); private: + void createTexture(); void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) override; diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 16464a3b4..9b1ce0f39 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -168,6 +168,11 @@ love::graphics::Texture *Graphics::newTexture(const love::graphics::Texture::Set return new Texture(this, settings, data); } +love::graphics::Texture *Graphics::newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) +{ + return new Texture(this, base, viewsettings); +} + love::graphics::Buffer *Graphics::newBuffer(const love::graphics::Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) { return new Buffer(this, settings, format, data, size, arraylength); diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index c5b164e0c..1726b200c 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -275,6 +275,7 @@ public: // implementation for virtual functions love::graphics::Texture *newTexture(const love::graphics::Texture::Settings &settings, const love::graphics::Texture::Slices *data) override; + love::graphics::Texture *newTextureView(love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) override; love::graphics::Buffer *newBuffer(const love::graphics::Buffer::Settings &settings, const std::vector& format, const void *data, size_t size, size_t arraylength) override; graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override; graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override; diff --git a/src/modules/graphics/vulkan/Texture.cpp b/src/modules/graphics/vulkan/Texture.cpp index 267847e7c..46a6a01cc 100644 --- a/src/modules/graphics/vulkan/Texture.cpp +++ b/src/modules/graphics/vulkan/Texture.cpp @@ -47,11 +47,22 @@ Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const slices.clear(); } +Texture::Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings) + : love::graphics::Texture(gfx, base, viewsettings) + , vgfx(dynamic_cast(gfx)) + , slices(viewsettings.type.get(base->getTextureType())) + , imageAspect(0) +{ + loadVolatile(); +} + bool Texture::loadVolatile() { allocator = vgfx->getVmaAllocator(); device = vgfx->getDevice(); + bool root = rootView.texture == this; + if (isPixelFormatDepth(format)) imageAspect |= VK_IMAGE_ASPECT_DEPTH_BIT; if (isPixelFormatStencil(format)) @@ -79,82 +90,96 @@ bool Texture::loadVolatile() usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } - VkImageCreateFlags createFlags = 0; - layerCount = 1; if (texType == TEXTURE_2D_ARRAY) layerCount = getLayerCount(); else if (texType == TEXTURE_CUBE) - { layerCount = 6; - createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - } - msaaSamples = vgfx->getMsaaCount(requestedMSAA); - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.flags = createFlags; - imageInfo.imageType = Vulkan::getImageType(getTextureType()); - imageInfo.extent.width = static_cast(pixelWidth); - imageInfo.extent.height = static_cast(pixelHeight); - imageInfo.extent.depth = static_cast(depth); - imageInfo.arrayLayers = static_cast(layerCount); - imageInfo.mipLevels = static_cast(mipmapCount); - imageInfo.format = vulkanFormat.internalFormat; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = usageFlags; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.samples = msaaSamples; - - VmaAllocationCreateInfo imageAllocationCreateInfo{}; - - if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS) - throw love::Exception("failed to create image"); - - auto commandBuffer = vgfx->getCommandBufferForDataTransfer(); - - if (isPixelFormatDepthStencil(format)) - imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - else if (computeWrite) - imageLayout = VK_IMAGE_LAYOUT_GENERAL; - else - imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, - VK_IMAGE_LAYOUT_UNDEFINED, imageLayout, - 0, VK_REMAINING_MIP_LEVELS, - 0, VK_REMAINING_ARRAY_LAYERS); - - bool hasdata = slices.get(0, 0) != nullptr; - - if (hasdata) + if (root) { - for (int mip = 0; mip < getMipmapCount(); mip++) - { - int sliceCount; - if (texType == TEXTURE_CUBE) - sliceCount = 6; - else - sliceCount = slices.getSliceCount(); + VkImageCreateFlags createFlags = 0; - for (int slice = 0; slice < sliceCount; slice++) + if (viewFormats) + createFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + + if (texType == TEXTURE_CUBE || texType == TEXTURE_2D_ARRAY) + createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + msaaSamples = vgfx->getMsaaCount(requestedMSAA); + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.flags = createFlags; + imageInfo.imageType = Vulkan::getImageType(getTextureType()); + imageInfo.extent.width = static_cast(pixelWidth); + imageInfo.extent.height = static_cast(pixelHeight); + imageInfo.extent.depth = static_cast(depth); + imageInfo.arrayLayers = static_cast(layerCount); + imageInfo.mipLevels = static_cast(mipmapCount); + imageInfo.format = vulkanFormat.internalFormat; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usageFlags; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = msaaSamples; + + VmaAllocationCreateInfo imageAllocationCreateInfo{}; + + if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS) + throw love::Exception("failed to create image"); + + auto commandBuffer = vgfx->getCommandBufferForDataTransfer(); + + if (isPixelFormatDepthStencil(format)) + imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + else if (computeWrite) + imageLayout = VK_IMAGE_LAYOUT_GENERAL; + else + imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_UNDEFINED, imageLayout, + 0, VK_REMAINING_MIP_LEVELS, + 0, VK_REMAINING_ARRAY_LAYERS); + + bool hasdata = slices.get(0, 0) != nullptr; + + if (hasdata) + { + for (int mip = 0; mip < getMipmapCount(); mip++) { - auto id = slices.get(slice, mip); - if (id != nullptr) - uploadImageData(id, mip, slice, 0, 0); + int sliceCount; + if (texType == TEXTURE_CUBE) + sliceCount = 6; + else + sliceCount = slices.getSliceCount(); + + for (int slice = 0; slice < sliceCount; slice++) + { + auto id = slices.get(slice, mip); + if (id != nullptr) + uploadImageData(id, mip, slice, 0, 0); + } } } + else + clear(); } else - clear(); + { + Texture *roottex = (Texture *) rootView.texture; + textureImage = roottex->textureImage; + textureImageAllocation = VK_NULL_HANDLE; + imageLayout = roottex->imageLayout; + msaaSamples = roottex->msaaSamples; + } createTextureImageView(); - textureSampler = vgfx->getCachedSampler(samplerState); + setSamplerState(samplerState); - if (!isPixelFormatDepthStencil(format) && slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE) + if (root && !isPixelFormatDepthStencil(format) && slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE) generateMipmaps(); if (renderTarget) @@ -172,9 +197,9 @@ bool Texture::loadVolatile() viewInfo.viewType = Vulkan::getImageViewType(getTextureType()); viewInfo.format = vulkanFormat.internalFormat; viewInfo.subresourceRange.aspectMask = imageAspect; - viewInfo.subresourceRange.baseMipLevel = mip; + viewInfo.subresourceRange.baseMipLevel = mip + rootView.startMipmap; viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = slice; + viewInfo.subresourceRange.baseArrayLayer = slice + rootView.startLayer; viewInfo.subresourceRange.layerCount = 1; viewInfo.components.r = vulkanFormat.swizzleR; viewInfo.components.g = vulkanFormat.swizzleG; @@ -189,15 +214,18 @@ bool Texture::loadVolatile() int64 memsize = 0; - for (int mip = 0; mip < getMipmapCount(); mip++) + if (root) { - int w = getPixelWidth(mip); - int h = getPixelHeight(mip); - int slices = getDepth(mip) * layerCount; - memsize += getPixelFormatSliceSize(format, w, h) * slices; - } + for (int mip = 0; mip < getMipmapCount(); mip++) + { + int w = getPixelWidth(mip); + int h = getPixelHeight(mip); + int slices = getDepth(mip) * layerCount; + memsize += getPixelFormatSliceSize(format, w, h) * slices; + } - memsize *= static_cast(msaaSamples); + memsize *= static_cast(msaaSamples); + } setGraphicsMemorySize(memsize); @@ -207,8 +235,16 @@ bool Texture::loadVolatile() { VkDebugUtilsObjectNameInfoEXT nameInfo{}; nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; - nameInfo.objectType = VK_OBJECT_TYPE_IMAGE; - nameInfo.objectHandle = (uint64_t)textureImage; + if (root) + { + nameInfo.objectType = VK_OBJECT_TYPE_IMAGE; + nameInfo.objectHandle = (uint64_t)textureImage; + } + else + { + nameInfo.objectType = VK_OBJECT_TYPE_IMAGE_VIEW; + nameInfo.objectHandle = (uint64_t)textureImageView; + } nameInfo.pObjectName = debugName.c_str(); vkSetDebugUtilsObjectNameEXT(device, &nameInfo); } @@ -230,13 +266,15 @@ void Texture::unloadVolatile() textureImageAllocation = textureImageAllocation, textureImageViews = std::move(renderTargetImageViews)] () { vkDestroyImageView(device, textureImageView, nullptr); - vmaDestroyImage(allocator, textureImage, textureImageAllocation); + if (textureImageAllocation) + vmaDestroyImage(allocator, textureImage, textureImageAllocation); for (const auto &views : textureImageViews) for (const auto &view : views) vkDestroyImageView(device, view, nullptr); }); textureImage = VK_NULL_HANDLE; + textureImageAllocation = VK_NULL_HANDLE; setGraphicsMemorySize(0); } @@ -278,8 +316,7 @@ ptrdiff_t Texture::getHandle() const void Texture::setSamplerState(const SamplerState &s) { - love::graphics::Texture::setSamplerState(s); - + samplerState = validateSamplerState(s); textureSampler = vgfx->getCachedSampler(samplerState); } @@ -291,16 +328,15 @@ VkImageLayout Texture::getImageLayout() const void Texture::createTextureImageView() { auto vulkanFormat = Vulkan::getTextureFormat(format); - VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = textureImage; viewInfo.viewType = Vulkan::getImageViewType(getTextureType()); viewInfo.format = vulkanFormat.internalFormat; viewInfo.subresourceRange.aspectMask = imageAspect; - viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.baseMipLevel = rootView.startMipmap; viewInfo.subresourceRange.levelCount = getMipmapCount(); - viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.baseArrayLayer = rootView.startLayer; viewInfo.subresourceRange.layerCount = layerCount; viewInfo.components.r = vulkanFormat.swizzleR; viewInfo.components.g = vulkanFormat.swizzleG; @@ -404,16 +440,16 @@ void Texture::generateMipmapsInternal() barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.baseArrayLayer = rootView.startLayer; barrier.subresourceRange.layerCount = static_cast(layerCount); - barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.baseMipLevel = rootView.startMipmap; barrier.subresourceRange.levelCount = 1u; uint32_t mipLevels = static_cast(getMipmapCount()); for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; + barrier.subresourceRange.baseMipLevel = rootView.startMipmap + i - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; @@ -429,15 +465,15 @@ void Texture::generateMipmapsInternal() blit.srcOffsets[0] = { 0, 0, 0 }; blit.srcOffsets[1] = { getPixelWidth(i - 1), getPixelHeight(i - 1), 1 }; blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - blit.srcSubresource.mipLevel = i - 1; - blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.mipLevel = rootView.startMipmap + i - 1; + blit.srcSubresource.baseArrayLayer = rootView.startLayer; blit.srcSubresource.layerCount = static_cast(layerCount); blit.dstOffsets[0] = { 0, 0, 0 }; blit.dstOffsets[1] = { getPixelWidth(i), getPixelHeight(i), 1 }; blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - blit.dstSubresource.mipLevel = i; - blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.mipLevel = rootView.startMipmap + i; + blit.dstSubresource.baseArrayLayer = rootView.startLayer; blit.dstSubresource.layerCount = static_cast(layerCount); vkCmdBlitImage(commandBuffer, @@ -458,7 +494,7 @@ void Texture::generateMipmapsInternal() 1, &barrier); } - barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.subresourceRange.baseMipLevel = rootView.startMipmap + mipLevels - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; @@ -496,11 +532,11 @@ void Texture::uploadByteData(const void *data, size_t size, int level, int slice region.bufferRowLength = 0; region.bufferImageHeight = 0; - uint32_t baseLayer; - if (getTextureType() == TEXTURE_VOLUME) - baseLayer = 0; - else - baseLayer = slice; + uint32_t baseLayer = rootView.startLayer; + if (getTextureType() != TEXTURE_VOLUME) + baseLayer += slice; + + level += rootView.startMipmap; region.imageSubresource.aspectMask = imageAspect; region.imageSubresource.mipLevel = level; @@ -558,8 +594,8 @@ void Texture::copyFromBuffer(graphics::Buffer *source, size_t sourceoffset, int VkImageSubresourceLayers layers{}; layers.aspectMask = imageAspect; - layers.mipLevel = mipmap; - layers.baseArrayLayer = slice; + layers.mipLevel = mipmap + rootView.startMipmap; + layers.baseArrayLayer = slice + rootView.startLayer; layers.layerCount = 1; VkBufferImageCopy region{}; @@ -588,8 +624,8 @@ void Texture::copyToBuffer(graphics::Buffer *dest, int slice, int mipmap, const VkImageSubresourceLayers layers{}; layers.aspectMask = imageAspect; - layers.mipLevel = mipmap; - layers.baseArrayLayer = slice; + layers.mipLevel = mipmap + rootView.startMipmap; + layers.baseArrayLayer = slice + rootView.startLayer; layers.layerCount = 1; VkBufferImageCopy region{}; diff --git a/src/modules/graphics/vulkan/Texture.h b/src/modules/graphics/vulkan/Texture.h index 4ec79be3c..9ad198378 100644 --- a/src/modules/graphics/vulkan/Texture.h +++ b/src/modules/graphics/vulkan/Texture.h @@ -40,11 +40,13 @@ class Texture final , public Volatile { public: - Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data); - ~Texture(); - virtual bool loadVolatile() override; - virtual void unloadVolatile() override; + Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data); + Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings); + virtual ~Texture(); + + bool loadVolatile() override; + void unloadVolatile() override; void setSamplerState(const SamplerState &s) override; @@ -61,7 +63,7 @@ public: void uploadByteData(const void *data, size_t size, int level, int slice, const Rect &r) override; - void generateMipmapsInternal() override; + void generateMipmapsInternal() override; int getMSAA() const override; ptrdiff_t getHandle() const override; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 52d2cecfe..e2221c9b6 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -799,6 +799,13 @@ static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool chec if (s.type == TEXTURE_2D_ARRAY || s.type == TEXTURE_VOLUME) s.layers = luax_checkintflag(L, idx, Texture::getConstant(Texture::SETTING_LAYERS)); } + else + { + s.width = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_WIDTH), s.width); + s.height = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_HEIGHT), s.height); + if (s.type == TEXTURE_2D_ARRAY || s.type == TEXTURE_VOLUME) + s.layers = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_LAYERS), s.layers); + } lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_MIPMAPS)); if (!lua_isnoneornil(L, -1)) @@ -823,6 +830,7 @@ static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool chec s.msaa = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_MSAA), s.msaa); s.computeWrite = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), s.computeWrite); + s.viewFormats = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_VIEW_FORMATS), s.viewFormats); lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_READABLE)); if (!lua_isnoneornil(L, -1)) @@ -1240,6 +1248,66 @@ int w_newVolumeImage(lua_State *L) return w_newVolumeTexture(L); } +int w_newTextureView(lua_State *L) +{ + Texture *base = luax_checktexture(L, 1); + luaL_checktype(L, 2, LUA_TTABLE); + + Texture::ViewSettings settings; + + lua_getfield(L, 2, "format"); + if (!lua_isnoneornil(L, -1)) + { + const char *str = luaL_checkstring(L, -1); + if (!getConstant(str, settings.format.value)) + luax_enumerror(L, "pixel format", str); + settings.format.hasValue = true; + } + lua_pop(L, 1); + + lua_getfield(L, 2, "type"); + if (!lua_isnoneornil(L, -1)) + { + const char *str = luaL_checkstring(L, -1); + if (!Texture::getConstant(str, settings.type.value)) + luax_enumerror(L, "texture type", Texture::getConstants(settings.type.value), str); + settings.type.hasValue = true; + } + lua_pop(L, 1); + + lua_getfield(L, 2, "mipmapstart"); + if (!lua_isnoneornil(L, -1)) + settings.mipmapStart.set(luaL_checkint(L, -1) - 1); + lua_pop(L, 1); + + lua_getfield(L, 2, "mipmapcount"); + if (!lua_isnoneornil(L, -1)) + settings.mipmapCount.set(luaL_checkint(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 2, "layerstart"); + if (!lua_isnoneornil(L, -1)) + settings.layerStart.set(luaL_checkint(L, -1) - 1); + lua_pop(L, 1); + + lua_getfield(L, 2, "layers"); + if (!lua_isnoneornil(L, -1)) + settings.layerCount.set(luaL_checkint(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 2, "debugname"); + if (!lua_isnoneornil(L, -1)) + settings.debugName = luaL_checkstring(L, -1); + lua_pop(L, 1); + + Texture *t = nullptr; + luax_catchexcept(L, [&]() { t = instance()->newTextureView(base, settings); }); + + luax_pushtype(L, t); + t->release(); + return 1; +} + int w_newQuad(lua_State *L) { luax_checkgraphicscreated(L); @@ -3857,6 +3925,7 @@ static const luaL_Reg functions[] = { "newCubeTexture", w_newCubeTexture }, { "newArrayTexture", w_newArrayTexture }, { "newVolumeTexture", w_newVolumeTexture }, + { "newTextureView", w_newTextureView }, { "newQuad", w_newQuad }, { "newFont", w_newFont }, { "newImageFont", w_newImageFont }, diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index 9381a02bc..533cb1997 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -300,6 +300,13 @@ int w_Texture_isReadable(lua_State *L) return 1; } +int w_Texture_hasViewFormats(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_pushboolean(L, t->hasViewFormats()); + return 1; +} + int w_Texture_setDepthSampleMode(lua_State *L) { Texture *t = luax_checktexture(L, 1); @@ -511,6 +518,7 @@ const luaL_Reg w_Texture_functions[] = { "isCanvas", w_Texture_isCanvas }, { "isComputeWritable", w_Texture_isComputeWritable }, { "isReadable", w_Texture_isReadable }, + { "hasViewFormats", w_Texture_hasViewFormats }, { "getMipmapMode", w_Texture_getMipmapMode }, { "getDepthSampleMode", w_Texture_getDepthSampleMode }, { "setDepthSampleMode", w_Texture_setDepthSampleMode },