flycast/core/rend/vulkan/texture.cpp

324 lines
12 KiB
C++
Raw Normal View History

2019-10-05 09:50:14 +00:00
/*
* Created on: Oct 3, 2019
Copyright 2019 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
2019-10-13 16:42:28 +00:00
#include <math.h>
2019-10-05 09:50:14 +00:00
#include "texture.h"
#include "utils.h"
2019-10-13 16:42:28 +00:00
void setImageLayout(vk::CommandBuffer const& commandBuffer, vk::Image image, vk::Format format, u32 mipmapLevels, vk::ImageLayout oldImageLayout, vk::ImageLayout newImageLayout)
2019-10-05 09:50:14 +00:00
{
vk::AccessFlags sourceAccessMask;
switch (oldImageLayout)
{
case vk::ImageLayout::eTransferDstOptimal:
sourceAccessMask = vk::AccessFlagBits::eTransferWrite;
break;
case vk::ImageLayout::eTransferSrcOptimal:
sourceAccessMask = vk::AccessFlagBits::eTransferRead;
break;
2019-10-05 09:50:14 +00:00
case vk::ImageLayout::ePreinitialized:
sourceAccessMask = vk::AccessFlagBits::eHostWrite;
break;
case vk::ImageLayout::eGeneral: // sourceAccessMask is empty
case vk::ImageLayout::eUndefined:
2019-10-09 19:16:12 +00:00
break;
case vk::ImageLayout::eShaderReadOnlyOptimal:
2019-10-09 19:16:12 +00:00
sourceAccessMask = vk::AccessFlagBits::eShaderRead;
2019-10-05 09:50:14 +00:00
break;
default:
verify(false);
break;
}
vk::PipelineStageFlags sourceStage;
switch (oldImageLayout)
{
case vk::ImageLayout::eGeneral:
case vk::ImageLayout::ePreinitialized:
sourceStage = vk::PipelineStageFlagBits::eHost;
break;
case vk::ImageLayout::eTransferDstOptimal:
case vk::ImageLayout::eTransferSrcOptimal:
2019-10-05 09:50:14 +00:00
sourceStage = vk::PipelineStageFlagBits::eTransfer;
break;
case vk::ImageLayout::eUndefined:
sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
break;
case vk::ImageLayout::eShaderReadOnlyOptimal:
sourceStage = vk::PipelineStageFlagBits::eFragmentShader;
break;
2019-10-05 09:50:14 +00:00
default:
verify(false);
break;
}
vk::AccessFlags destinationAccessMask;
switch (newImageLayout)
{
case vk::ImageLayout::eColorAttachmentOptimal:
destinationAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
break;
case vk::ImageLayout::eDepthStencilAttachmentOptimal:
destinationAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
break;
case vk::ImageLayout::eGeneral: // empty destinationAccessMask
2019-11-26 09:42:44 +00:00
break;
2019-10-05 09:50:14 +00:00
case vk::ImageLayout::eShaderReadOnlyOptimal:
destinationAccessMask = vk::AccessFlagBits::eShaderRead;
break;
case vk::ImageLayout::eTransferSrcOptimal:
destinationAccessMask = vk::AccessFlagBits::eTransferRead;
break;
case vk::ImageLayout::eTransferDstOptimal:
destinationAccessMask = vk::AccessFlagBits::eTransferWrite;
break;
case vk::ImageLayout::eDepthStencilReadOnlyOptimal:
destinationAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead;
break;
2019-10-05 09:50:14 +00:00
default:
verify(false);
break;
}
vk::PipelineStageFlags destinationStage;
switch (newImageLayout)
{
case vk::ImageLayout::eColorAttachmentOptimal:
destinationStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
break;
case vk::ImageLayout::eDepthStencilAttachmentOptimal:
destinationStage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
break;
case vk::ImageLayout::eGeneral:
destinationStage = vk::PipelineStageFlagBits::eHost;
break;
case vk::ImageLayout::eShaderReadOnlyOptimal:
destinationStage = vk::PipelineStageFlagBits::eFragmentShader;
break;
case vk::ImageLayout::eTransferDstOptimal:
case vk::ImageLayout::eTransferSrcOptimal:
destinationStage = vk::PipelineStageFlagBits::eTransfer;
break;
case vk::ImageLayout::eDepthStencilReadOnlyOptimal:
destinationStage = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests;
break;
2019-10-05 09:50:14 +00:00
default:
verify(false);
break;
}
vk::ImageAspectFlags aspectMask;
if (newImageLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal || newImageLayout == vk::ImageLayout::eDepthStencilReadOnlyOptimal)
2019-10-05 09:50:14 +00:00
{
aspectMask = vk::ImageAspectFlagBits::eDepth;
if (format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint || format == vk::Format::eD16UnormS8Uint)
2019-10-05 09:50:14 +00:00
{
aspectMask |= vk::ImageAspectFlagBits::eStencil;
}
}
else
{
aspectMask = vk::ImageAspectFlagBits::eColor;
}
2019-10-13 16:42:28 +00:00
vk::ImageSubresourceRange imageSubresourceRange(aspectMask, 0, mipmapLevels, 0, 1);
2019-10-05 09:50:14 +00:00
vk::ImageMemoryBarrier imageMemoryBarrier(sourceAccessMask, destinationAccessMask, oldImageLayout, newImageLayout, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, image, imageSubresourceRange);
commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, nullptr, nullptr, imageMemoryBarrier);
}
void Texture::UploadToGPU(int width, int height, u8 *data, bool mipmapped)
2019-10-05 09:50:14 +00:00
{
vk::Format format;
u32 dataSize = width * height * 2;
switch (tex_type)
{
case TextureType::_5551:
format = vk::Format::eR5G5B5A1UnormPack16;
break;
case TextureType::_565:
format = vk::Format::eR5G6B5UnormPack16;
break;
case TextureType::_4444:
format = vk::Format::eR4G4B4A4UnormPack16;
break;
case TextureType::_8888:
format = vk::Format::eR8G8B8A8Unorm;
dataSize *= 2;
break;
2019-10-06 15:02:17 +00:00
case TextureType::_8:
format = vk::Format::eR8Unorm;
dataSize /= 2;
break;
2019-10-05 09:50:14 +00:00
}
if (mipmapped)
{
int w = width / 2;
u32 size = dataSize / 4;
while (w)
{
dataSize += size;
size /= 4;
w /= 2;
}
}
bool isNew = true;
if (width != extent.width || height != extent.height || format != this->format)
Init(width, height, format, dataSize, mipmapped);
else
isNew = false;
SetImage(dataSize, data, isNew);
2019-10-05 09:50:14 +00:00
}
void Texture::Init(u32 width, u32 height, vk::Format format, u32 dataSize, bool mipmapped)
2019-10-05 09:50:14 +00:00
{
this->extent = vk::Extent2D(width, height);
this->format = format;
2019-10-13 16:42:28 +00:00
mipmapLevels = 1;
if (mipmapped)
2019-10-13 16:42:28 +00:00
mipmapLevels += floor(log2(std::max(width, height)));
2019-10-05 09:50:14 +00:00
vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(format);
2019-11-26 09:42:44 +00:00
vk::ImageTiling imageTiling = (formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage)
== vk::FormatFeatureFlagBits::eSampledImage
? vk::ImageTiling::eOptimal
: vk::ImageTiling::eLinear;
needsStaging = imageTiling != vk::ImageTiling::eLinear;
2019-10-05 09:50:14 +00:00
vk::ImageLayout initialLayout;
vk::MemoryPropertyFlags requirements;
vk::ImageUsageFlags usageFlags = vk::ImageUsageFlagBits::eSampled;
if (needsStaging)
{
stagingBufferData = std::unique_ptr<BufferData>(new BufferData(dataSize, vk::BufferUsageFlagBits::eTransferSrc));
2019-10-05 09:50:14 +00:00
usageFlags |= vk::ImageUsageFlagBits::eTransferDst;
initialLayout = vk::ImageLayout::eUndefined;
2019-10-12 11:47:25 +00:00
requirements = vk::MemoryPropertyFlagBits::eDeviceLocal;
2019-10-05 09:50:14 +00:00
}
else
{
2019-11-26 09:42:44 +00:00
verify((formatProperties.linearTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage) == vk::FormatFeatureFlagBits::eSampledImage);
2019-10-05 09:50:14 +00:00
initialLayout = vk::ImageLayout::ePreinitialized;
requirements = vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible;
}
2019-10-12 11:47:25 +00:00
CreateImage(imageTiling, usageFlags, initialLayout, requirements, vk::ImageAspectFlagBits::eColor);
2019-10-05 09:50:14 +00:00
}
void Texture::CreateImage(vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::ImageLayout initialLayout,
vk::MemoryPropertyFlags memoryProperties, vk::ImageAspectFlags aspectMask)
{
2019-10-13 16:42:28 +00:00
vk::ImageCreateInfo imageCreateInfo(vk::ImageCreateFlags(), vk::ImageType::e2D, format, vk::Extent3D(extent, 1), mipmapLevels, 1,
2019-10-05 09:50:14 +00:00
vk::SampleCountFlagBits::e1, tiling, usage,
vk::SharingMode::eExclusive, 0, nullptr, initialLayout);
image = device.createImageUnique(imageCreateInfo);
2019-11-26 09:42:44 +00:00
VmaAllocationCreateInfo allocCreateInfo = { VmaAllocationCreateFlags(), VmaMemoryUsage::VMA_MEMORY_USAGE_GPU_ONLY };
if (!needsStaging)
allocCreateInfo.flags = VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT;
allocation = VulkanContext::Instance()->GetAllocator().AllocateForImage(*image, allocCreateInfo);
2019-10-05 09:50:14 +00:00
vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image.get(), vk::ImageViewType::e2D, format, vk::ComponentMapping(),
2019-10-13 16:42:28 +00:00
vk::ImageSubresourceRange(aspectMask, 0, mipmapLevels, 0, 1));
2019-10-05 09:50:14 +00:00
imageView = device.createImageViewUnique(imageViewCreateInfo);
}
void Texture::SetImage(u32 srcSize, void *srcData, bool isNew)
2019-10-05 09:50:14 +00:00
{
verify((bool)commandBuffer);
commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));
2019-11-26 09:42:44 +00:00
// if (!isNew && !needsStaging)
// setImageLayout(commandBuffer, image.get(), format, mipmapLevels, vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageLayout::eUndefined);
void* data;
if (needsStaging)
data = stagingBufferData->MapMemory();
else
data = allocation.MapMemory();
verify(data != nullptr);
2019-10-05 09:50:14 +00:00
memcpy(data, srcData, srcSize);
if (needsStaging)
{
2019-10-12 11:47:25 +00:00
stagingBufferData->UnmapMemory();
2019-10-05 09:50:14 +00:00
// Since we're going to blit to the texture image, set its layout to eTransferDstOptimal
2019-10-13 16:42:28 +00:00
setImageLayout(commandBuffer, image.get(), format, mipmapLevels, isNew ? vk::ImageLayout::eUndefined : vk::ImageLayout::eShaderReadOnlyOptimal,
vk::ImageLayout::eTransferDstOptimal);
2019-10-13 16:42:28 +00:00
if (mipmapLevels > 1)
{
vk::DeviceSize bufferOffset = 0;
for (int i = 0; i < mipmapLevels; i++)
{
vk::BufferImageCopy copyRegion(bufferOffset, 1 << i, 1 << i, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, mipmapLevels - i - 1, 0, 1),
vk::Offset3D(0, 0, 0), vk::Extent3D(1 << i, 1 << i, 1));
commandBuffer.copyBufferToImage(stagingBufferData->buffer.get(), image.get(), vk::ImageLayout::eTransferDstOptimal, copyRegion);
bufferOffset += (1 << (2 * i)) * (tex_type == TextureType::_8888 ? 4 : 2);
}
2019-10-13 16:42:28 +00:00
}
else
{
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);
2019-10-13 16:42:28 +00:00
}
// 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);
2019-10-13 16:42:28 +00:00
}
commandBuffer.end();
2019-10-13 16:42:28 +00:00
}
void FramebufferAttachment::Init(u32 width, u32 height, vk::Format format, vk::ImageUsageFlags usage)
2019-10-09 19:16:12 +00:00
{
this->format = format;
this->extent = vk::Extent2D { width, height };
bool depth = format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint || format == vk::Format::eD16UnormS8Uint;
2019-10-09 19:16:12 +00:00
if (usage & vk::ImageUsageFlagBits::eTransferSrc)
2019-10-09 19:16:12 +00:00
{
stagingBufferData = std::unique_ptr<BufferData>(new BufferData(width * height * 4,
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst));
2019-10-09 19:16:12 +00:00
}
vk::ImageCreateInfo imageCreateInfo(vk::ImageCreateFlags(), vk::ImageType::e2D, format, vk::Extent3D(extent, 1), 1, 1, vk::SampleCountFlagBits::e1,
vk::ImageTiling::eOptimal, usage,
2019-10-09 19:16:12 +00:00
vk::SharingMode::eExclusive, 0, nullptr, vk::ImageLayout::eUndefined);
image = device.createImageUnique(imageCreateInfo);
2019-11-26 09:42:44 +00:00
VmaAllocationCreateInfo allocCreateInfo = { VmaAllocationCreateFlags(), VmaMemoryUsage::VMA_MEMORY_USAGE_GPU_ONLY };
if (usage & vk::ImageUsageFlagBits::eTransientAttachment)
allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
2019-11-26 09:42:44 +00:00
allocation = VulkanContext::Instance()->GetAllocator().AllocateForImage(*image, allocCreateInfo);
2019-10-09 19:16:12 +00:00
vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image.get(), vk::ImageViewType::e2D,
format, vk::ComponentMapping(), vk::ImageSubresourceRange(depth ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
imageView = device.createImageViewUnique(imageViewCreateInfo);
if ((usage & vk::ImageUsageFlagBits::eDepthStencilAttachment) && (usage & vk::ImageUsageFlagBits::eInputAttachment))
{
// Also create an imageView for the stencil
imageViewCreateInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1);
stencilView = device.createImageViewUnique(imageViewCreateInfo);
}
2019-10-09 19:16:12 +00:00
}