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 },