232 lines
8.8 KiB
C++
232 lines
8.8 KiB
C++
/*
|
|
* 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/>.
|
|
*/
|
|
#include "texture.h"
|
|
#include "utils.h"
|
|
|
|
static void setImageLayout(vk::CommandBuffer const& commandBuffer, vk::Image image, vk::Format format, vk::ImageLayout oldImageLayout, vk::ImageLayout newImageLayout)
|
|
{
|
|
vk::AccessFlags sourceAccessMask;
|
|
switch (oldImageLayout)
|
|
{
|
|
case vk::ImageLayout::eTransferDstOptimal:
|
|
sourceAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
break;
|
|
case vk::ImageLayout::ePreinitialized:
|
|
sourceAccessMask = vk::AccessFlagBits::eHostWrite;
|
|
break;
|
|
case vk::ImageLayout::eGeneral: // sourceAccessMask is empty
|
|
case vk::ImageLayout::eUndefined:
|
|
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:
|
|
sourceStage = vk::PipelineStageFlagBits::eTransfer;
|
|
break;
|
|
case vk::ImageLayout::eUndefined:
|
|
sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
|
break;
|
|
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
|
|
break;
|
|
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;
|
|
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;
|
|
default:
|
|
verify(false);
|
|
break;
|
|
}
|
|
|
|
vk::ImageAspectFlags aspectMask;
|
|
if (newImageLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal)
|
|
{
|
|
aspectMask = vk::ImageAspectFlagBits::eDepth;
|
|
if (format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint)
|
|
{
|
|
aspectMask |= vk::ImageAspectFlagBits::eStencil;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
}
|
|
|
|
vk::ImageSubresourceRange imageSubresourceRange(aspectMask, 0, 1, 0, 1);
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
Init(width, height, format);
|
|
SetImage(VulkanContext::Instance()->GetCurrentCommandBuffer(), dataSize, data);
|
|
samplers.clear();
|
|
}
|
|
|
|
void Texture::Init(u32 width, u32 height, vk::Format format)
|
|
{
|
|
this->extent = vk::Extent2D(width, height);
|
|
this->format = format;
|
|
vk::PhysicalDeviceMemoryProperties memoryProperties = physicalDevice.getMemoryProperties();
|
|
vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(format);
|
|
|
|
vk::FormatFeatureFlags formatFeatureFlags = vk::FormatFeatureFlagBits::eSampledImage;
|
|
needsStaging = (formatProperties.linearTilingFeatures & formatFeatureFlags) != formatFeatureFlags;
|
|
vk::ImageTiling imageTiling;
|
|
vk::ImageLayout initialLayout;
|
|
vk::MemoryPropertyFlags requirements;
|
|
vk::ImageUsageFlags usageFlags = vk::ImageUsageFlagBits::eSampled;
|
|
if (needsStaging)
|
|
{
|
|
printf("Texture::CreateSampler needsStaging\n");
|
|
verify((formatProperties.optimalTilingFeatures & formatFeatureFlags) == formatFeatureFlags);
|
|
stagingBufferData = std::unique_ptr<BufferData>(new BufferData(physicalDevice, device, extent.width * extent.height * 4, vk::BufferUsageFlagBits::eTransferSrc));
|
|
imageTiling = vk::ImageTiling::eOptimal;
|
|
usageFlags |= vk::ImageUsageFlagBits::eTransferDst;
|
|
initialLayout = vk::ImageLayout::eUndefined;
|
|
}
|
|
else
|
|
{
|
|
imageTiling = vk::ImageTiling::eLinear;
|
|
initialLayout = vk::ImageLayout::ePreinitialized;
|
|
requirements = vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible;
|
|
}
|
|
CreateImage(imageTiling, usageFlags | vk::ImageUsageFlagBits::eSampled, initialLayout, requirements,
|
|
vk::ImageAspectFlagBits::eColor);
|
|
}
|
|
|
|
void Texture::CreateImage(vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::ImageLayout initialLayout,
|
|
vk::MemoryPropertyFlags memoryProperties, vk::ImageAspectFlags aspectMask)
|
|
{
|
|
vk::ImageCreateInfo imageCreateInfo(vk::ImageCreateFlags(), vk::ImageType::e2D, format, vk::Extent3D(extent, 1), 1, 1,
|
|
vk::SampleCountFlagBits::e1, tiling, usage,
|
|
vk::SharingMode::eExclusive, 0, nullptr, initialLayout);
|
|
image = device.createImageUnique(imageCreateInfo);
|
|
|
|
vk::MemoryRequirements memReq = device.getImageMemoryRequirements(image.get());
|
|
u32 memoryTypeIndex = findMemoryType(physicalDevice.getMemoryProperties(), memReq.memoryTypeBits, memoryProperties);
|
|
deviceMemory = device.allocateMemoryUnique(vk::MemoryAllocateInfo(memReq.size, memoryTypeIndex));
|
|
|
|
device.bindImageMemory(image.get(), deviceMemory.get(), 0);
|
|
|
|
vk::ComponentMapping componentMapping(vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA);
|
|
vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image.get(), vk::ImageViewType::e2D, format, componentMapping,
|
|
vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1));
|
|
imageView = device.createImageViewUnique(imageViewCreateInfo);
|
|
}
|
|
|
|
void Texture::SetImage(vk::CommandBuffer const& commandBuffer, u32 srcSize, void *srcData)
|
|
{
|
|
vk::DeviceSize size = needsStaging
|
|
? device.getBufferMemoryRequirements(stagingBufferData->buffer.get()).size
|
|
: device.getImageMemoryRequirements(image.get()).size;
|
|
void* data = needsStaging
|
|
? device.mapMemory(stagingBufferData->deviceMemory.get(), 0, size)
|
|
: device.mapMemory(deviceMemory.get(), 0, size);
|
|
memcpy(data, srcData, srcSize);
|
|
device.unmapMemory(needsStaging ? stagingBufferData->deviceMemory.get() : deviceMemory.get());
|
|
|
|
if (needsStaging)
|
|
{
|
|
// Since we're going to blit to the texture image, set its layout to eTransferDstOptimal
|
|
setImageLayout(commandBuffer, image.get(), format, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
|
|
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);
|
|
// Set the layout for the texture image from eTransferDstOptimal to SHADER_READ_ONLY
|
|
setImageLayout(commandBuffer, image.get(), format, 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, vk::ImageLayout::ePreinitialized, vk::ImageLayout::eShaderReadOnlyOptimal);
|
|
}
|
|
}
|