diff --git a/core/rend/TexCache.cpp b/core/rend/TexCache.cpp index da2b06fb2..0aa54a268 100644 --- a/core/rend/TexCache.cpp +++ b/core/rend/TexCache.cpp @@ -636,7 +636,7 @@ void BaseTextureCacheData::Update() need_32bit_buffer = false; // TODO avoid upscaling/depost. textures that change too often - bool mipmapped = IsMipmapped() && settings.rend.UseMipmaps && !settings.rend.DumpTextures; + bool mipmapped = IsMipmapped() && !settings.rend.DumpTextures; if (texconv32 != NULL && need_32bit_buffer) { @@ -753,7 +753,7 @@ void BaseTextureCacheData::Update() //lock the texture to detect changes in it lock_block = libCore_vramlock_Lock(sa_tex,sa+size-1,this); - UploadToGPU(upscaled_w, upscaled_h, (u8*)temp_tex_buffer, mipmapped); + UploadToGPU(upscaled_w, upscaled_h, (u8*)temp_tex_buffer, mipmapped, mipmapped); if (settings.rend.DumpTextures) { ComputeHash(); @@ -768,7 +768,7 @@ void BaseTextureCacheData::CheckCustomTexture() if (IsCustomTextureAvailable()) { tex_type = TextureType::_8888; - UploadToGPU(custom_width, custom_height, custom_image_data, false); + UploadToGPU(custom_width, custom_height, custom_image_data, IsMipmapped(), false); delete [] custom_image_data; custom_image_data = NULL; } diff --git a/core/rend/TexCache.h b/core/rend/TexCache.h index 0e85e7eab..ffa0ea29c 100644 --- a/core/rend/TexCache.h +++ b/core/rend/TexCache.h @@ -689,7 +689,7 @@ struct BaseTextureCacheData bool IsMipmapped() { - return tcw.MipMapped != 0 && tcw.ScanOrder == 0; + return tcw.MipMapped != 0 && tcw.ScanOrder == 0 && settings.rend.UseMipmaps; } const char* GetPixelFormatName() @@ -715,7 +715,7 @@ struct BaseTextureCacheData void Create(); void ComputeHash(); void Update(); - virtual void UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped) = 0; + virtual void UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded = false) = 0; virtual bool Force32BitTexture(TextureType type) const { return false; } void CheckCustomTexture(); //true if : dirty or paletted texture and hashes don't match diff --git a/core/rend/gles/gles.h b/core/rend/gles/gles.h index c46ea26be..012e8241f 100755 --- a/core/rend/gles/gles.h +++ b/core/rend/gles/gles.h @@ -200,7 +200,7 @@ struct TextureCacheData : BaseTextureCacheData { GLuint texID; //gl texture virtual std::string GetId() override { return std::to_string(texID); } - virtual void UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped) override; + virtual void UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded = false) override; virtual bool Delete() override; }; diff --git a/core/rend/gles/gltex.cpp b/core/rend/gles/gltex.cpp index a396baa8e..6b01d173f 100644 --- a/core/rend/gles/gltex.cpp +++ b/core/rend/gles/gltex.cpp @@ -69,7 +69,7 @@ static void dumpRtTexture(u32 name, u32 w, u32 h) { free(rows); } -void TextureCacheData::UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped) +void TextureCacheData::UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded) { //upload to OpenGL ! glcache.BindTexture(GL_TEXTURE_2D, texID); @@ -94,7 +94,7 @@ void TextureCacheData::UploadToGPU(int width, int height, u8 *temp_tex_buffer, b die("Unsupported texture type"); break; } - if (mipmapped) + if (mipmapsIncluded) { int mipmapLevels = 0; int dim = width; @@ -149,9 +149,9 @@ void TextureCacheData::UploadToGPU(int width, int height, u8 *temp_tex_buffer, b } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexImage2D(GL_TEXTURE_2D, 0,comps, width, height, 0, comps, gltype, temp_tex_buffer); + if (mipmapped) + glGenerateMipmap(GL_TEXTURE_2D); } glCheck(); } diff --git a/core/rend/vulkan/texture.cpp b/core/rend/vulkan/texture.cpp index 22b6f531f..edf8fc388 100644 --- a/core/rend/vulkan/texture.cpp +++ b/core/rend/vulkan/texture.cpp @@ -143,7 +143,7 @@ void setImageLayout(vk::CommandBuffer const& commandBuffer, vk::Image image, vk: commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, nullptr, nullptr, imageMemoryBarrier); } -void Texture::UploadToGPU(int width, int height, u8 *data, bool mipmapped) +void Texture::UploadToGPU(int width, int height, u8 *data, bool mipmapped, bool mipmapsIncluded) { vk::Format format; u32 dataSize = width * height * 2; @@ -167,7 +167,7 @@ void Texture::UploadToGPU(int width, int height, u8 *data, bool mipmapped) dataSize /= 2; break; } - if (mipmapped) + if (mipmapsIncluded) { int w = width / 2; u32 size = dataSize / 4; @@ -180,13 +180,13 @@ void Texture::UploadToGPU(int width, int height, u8 *data, bool mipmapped) } bool isNew = true; if (width != extent.width || height != extent.height || format != this->format) - Init(width, height, format, dataSize, mipmapped); + Init(width, height, format, dataSize, mipmapped, mipmapsIncluded); else isNew = false; - SetImage(dataSize, data, isNew); + SetImage(dataSize, data, isNew, mipmapped && !mipmapsIncluded); } -void Texture::Init(u32 width, u32 height, vk::Format format, u32 dataSize, bool mipmapped) +void Texture::Init(u32 width, u32 height, vk::Format format, u32 dataSize, bool mipmapped, bool mipmapsIncluded) { this->extent = vk::Extent2D(width, height); this->format = format; @@ -217,6 +217,8 @@ void Texture::Init(u32 width, u32 height, vk::Format format, u32 dataSize, bool initialLayout = vk::ImageLayout::ePreinitialized; requirements = vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible; } + if (mipmapped && !mipmapsIncluded) + usageFlags |= vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst; CreateImage(imageTiling, usageFlags, initialLayout, requirements, vk::ImageAspectFlagBits::eColor); } @@ -238,7 +240,7 @@ void Texture::CreateImage(vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk: imageView = device.createImageViewUnique(imageViewCreateInfo); } -void Texture::SetImage(u32 srcSize, void *srcData, bool isNew) +void Texture::SetImage(u32 srcSize, void *srcData, bool isNew, bool genMipmaps) { verify((bool)commandBuffer); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); @@ -261,7 +263,8 @@ void Texture::SetImage(u32 srcSize, void *srcData, bool isNew) // Since we're going to blit to the texture image, set its layout to eTransferDstOptimal setImageLayout(commandBuffer, image.get(), format, mipmapLevels, isNew ? vk::ImageLayout::eUndefined : vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageLayout::eTransferDstOptimal); - if (mipmapLevels > 1) + + if (mipmapLevels > 1 && !genMipmaps) { vk::DeviceSize bufferOffset = 0; for (int i = 0; i < mipmapLevels; i++) @@ -277,18 +280,75 @@ void Texture::SetImage(u32 srcSize, void *srcData, bool isNew) vk::BufferImageCopy copyRegion(0, extent.width, extent.height, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), vk::Offset3D(0, 0, 0), vk::Extent3D(extent, 1)); commandBuffer.copyBufferToImage(stagingBufferData->buffer.get(), image.get(), vk::ImageLayout::eTransferDstOptimal, copyRegion); + if (mipmapLevels > 1) + GenerateMipmaps(); } // Set the layout for the texture image from eTransferDstOptimal to SHADER_READ_ONLY setImageLayout(commandBuffer, image.get(), format, mipmapLevels, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); } else { - // If we can use the linear tiled image as a texture, just do it - setImageLayout(commandBuffer, image.get(), format, mipmapLevels, vk::ImageLayout::ePreinitialized, vk::ImageLayout::eShaderReadOnlyOptimal); + if (mipmapLevels > 1) + GenerateMipmaps(); + else + // If we can use the linear tiled image as a texture, just do it + setImageLayout(commandBuffer, image.get(), format, mipmapLevels, vk::ImageLayout::ePreinitialized, vk::ImageLayout::eShaderReadOnlyOptimal); } commandBuffer.end(); } +void Texture::GenerateMipmaps() +{ + u32 mipWidth = extent.width; + u32 mipHeight = extent.height; + vk::ImageMemoryBarrier barrier(vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eTransferRead, + vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, + *image, vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + + for (int i = 1; i < mipmapLevels; i++) + { + // Transition previous mipmap level from dst optimal/preinit to src optimal + barrier.subresourceRange.baseMipLevel = i - 1; + if (i == 1 && !needsStaging) + { + barrier.oldLayout = vk::ImageLayout::ePreinitialized; + barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite; + } + else + { + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + } + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, nullptr, barrier); + + // Blit previous mipmap level on current + vk::ImageBlit blit(vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1), + { { vk::Offset3D(0, 0, 0), vk::Offset3D(mipWidth, mipHeight, 1) } }, + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1), + { { vk::Offset3D(0, 0, 0), vk::Offset3D(std::max(mipWidth / 2, 1u), std::max(mipHeight / 2, 1u), 1) } }); + commandBuffer.blitImage(*image, vk::ImageLayout::eTransferSrcOptimal, *image, vk::ImageLayout::eTransferDstOptimal, 1, &blit, vk::Filter::eLinear); + + // Transition previous mipmap level from src optimal to shader read-only optimal + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr, barrier); + + mipWidth = std::max(mipWidth / 2, 1u); + mipHeight = std::max(mipHeight / 2, 1u); + } + // Transition last mipmap level from dst optimal to shader read-only optimal + barrier.subresourceRange.baseMipLevel = mipmapLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr, barrier); +} + void FramebufferAttachment::Init(u32 width, u32 height, vk::Format format, vk::ImageUsageFlags usage) { this->format = format; diff --git a/core/rend/vulkan/texture.h b/core/rend/vulkan/texture.h index 1fe02bdda..d7799a62c 100644 --- a/core/rend/vulkan/texture.h +++ b/core/rend/vulkan/texture.h @@ -30,7 +30,7 @@ void setImageLayout(vk::CommandBuffer const& commandBuffer, vk::Image image, vk: struct Texture : BaseTextureCacheData { - void UploadToGPU(int width, int height, u8 *data, bool mipmapped) override; + void UploadToGPU(int width, int height, u8 *data, bool mipmapped, bool mipmapsIncluded = false) override; u64 GetIntId() { return (u64)reinterpret_cast(this); } std::string GetId() override { char s[20]; sprintf(s, "%p", this); return s; } bool IsNew() const { return !image.get(); } @@ -43,10 +43,11 @@ struct Texture : BaseTextureCacheData void SetDevice(vk::Device device) { this->device = device; } private: - void Init(u32 width, u32 height, vk::Format format ,u32 dataSize, bool mipmapped); - void SetImage(u32 size, void *data, bool isNew); + void Init(u32 width, u32 height, vk::Format format ,u32 dataSize, bool mipmapped, bool mipmapsIncluded); + void SetImage(u32 size, void *data, bool isNew, bool genMipmaps); void CreateImage(vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::ImageLayout initialLayout, vk::MemoryPropertyFlags memoryProperties, vk::ImageAspectFlags aspectMask); + void GenerateMipmaps(); vk::Format format = vk::Format::eUndefined; vk::Extent2D extent;