Use an empty host texture in place of invalid TIC entries on guest

Some games may pass empty TICs as inputs to shaders while not actually using them within the shader. Create an empty texture and pass this in instead when we hit this case, the nullDescriptor feature could be used but it's not supported by all devices so we chose to do it this way instead.
This commit is contained in:
Billy Laws 2022-04-11 20:10:53 +01:00 committed by PixelyIon
parent 41b98c7daa
commit 8eaca87de8
3 changed files with 47 additions and 11 deletions

View File

@ -70,6 +70,35 @@ namespace skyline::gpu::interconnect {
if (!gpu.traits.supportsLastProvokingVertex)
rasterizerState.unlink<vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT>();
// Set of default parameters for null image which we use instead of a null descriptor since not all devices support that extension
constexpr texture::Format NullImageFormat{format::R8G8B8A8Unorm};
constexpr texture::Dimensions NullImageDimensions{1, 1, 1};
constexpr vk::ImageLayout NullImageInitialLayout{vk::ImageLayout::eUndefined};
constexpr vk::ImageTiling NullImageTiling{vk::ImageTiling::eOptimal};
auto vkImage{gpu.memory.AllocateImage({
.imageType = vk::ImageType::e2D,
.format = NullImageFormat->vkFormat,
.extent = NullImageDimensions,
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = NullImageTiling,
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
.sharingMode = vk::SharingMode::eExclusive,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &gpu.vkQueueFamilyIndex,
.initialLayout = NullImageInitialLayout
})};
auto nullTexture{std::make_shared<Texture>(gpu, std::move(vkImage), NullImageDimensions, NullImageFormat, NullImageInitialLayout, NullImageTiling)};
nullTexture->TransitionLayout(vk::ImageLayout::eGeneral);
nullTextureView = nullTexture->GetView(vk::ImageViewType::e2D, vk::ImageSubresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
});
}
/* Render Targets + Render Target Control */
@ -1769,6 +1798,7 @@ namespace skyline::gpu::interconnect {
/* Textures */
private:
u32 bindlessTextureConstantBufferIndex{};
std::shared_ptr<TextureView> nullTextureView; //!< View used instead of a null descriptor when an empty TIC is encountered, this avoids the need for the nullDescriptor VK feature
struct PoolTexture : public FenceCycleDependency {
GuestTexture guest;
@ -1961,6 +1991,11 @@ namespace skyline::gpu::interconnect {
auto textureIt{texturePool.textures.insert({textureControl, {}})};
auto &poolTexture{textureIt.first->second};
if (textureIt.second) {
if (textureControl.formatWord.format == TextureImageControl::ImageFormat::Invalid) {
poolTexture.view = nullTextureView;
return nullTextureView;
}
// If the entry didn't exist prior then we need to convert the TIC to a GuestTexture
auto &guest{poolTexture.guest};
guest.format = ConvertTicFormat(textureControl.formatWord, textureControl.isSrgb);

View File

@ -22,6 +22,7 @@ namespace skyline::gpu::interconnect {
* @note An underscore may be used to describe a different block in a format
*/
enum class ImageFormat : u32 {
Invalid = 0x0,
R32G32B32A32 = 0x01,
R32G32B32 = 0x02,
R16G16B16A16 = 0x03,

View File

@ -415,8 +415,8 @@ namespace skyline::gpu {
}
void Texture::SynchronizeHost(bool rwTrap) {
if (dirtyState != DirtyState::CpuDirty)
return; // If the texture has not been modified on the CPU, there is no need to synchronize it
if (dirtyState != DirtyState::CpuDirty || !guest)
return; // If the texture has not been modified on the CPU or has no mappings, there is no need to synchronize it
TRACE_EVENT("gpu", "Texture::SynchronizeHost");
@ -439,7 +439,7 @@ namespace skyline::gpu {
}
void Texture::SynchronizeHostWithBuffer(const vk::raii::CommandBuffer &commandBuffer, const std::shared_ptr<FenceCycle> &pCycle, bool rwTrap) {
if (dirtyState != DirtyState::CpuDirty)
if (dirtyState != DirtyState::CpuDirty || !guest)
return;
TRACE_EVENT("gpu", "Texture::SynchronizeHostWithBuffer");
@ -461,13 +461,12 @@ namespace skyline::gpu {
}
void Texture::SynchronizeGuest(bool skipTrap) {
if (dirtyState != DirtyState::GpuDirty || layout == vk::ImageLayout::eUndefined) {
// We can skip syncing in two cases:
if (dirtyState != DirtyState::GpuDirty || layout == vk::ImageLayout::eUndefined || !guest) {
// We can skip syncing in three cases:
// * If the texture has not been used on the GPU, there is no need to synchronize it
// * If the state of the host texture is undefined then so can the guest
// * If there is no guest texture to synchronise
return;
} else if (!guest) {
throw exception("Synchronization of guest textures requires a valid guest texture to synchronize to");
}
TRACE_EVENT("gpu", "Texture::SynchronizeGuest");
@ -497,12 +496,10 @@ namespace skyline::gpu {
}
void Texture::SynchronizeGuestWithBuffer(const vk::raii::CommandBuffer &commandBuffer, const std::shared_ptr<FenceCycle> &pCycle) {
if (dirtyState != DirtyState::GpuDirty)
if (dirtyState != DirtyState::GpuDirty || !guest)
return;
if (!guest)
throw exception("Synchronization of guest textures requires a valid guest texture to synchronize to");
else if (layout == vk::ImageLayout::eUndefined)
if (layout == vk::ImageLayout::eUndefined)
return; // If the state of the host texture is undefined then so can the guest
TRACE_EVENT("gpu", "Texture::SynchronizeGuestWithBuffer");
@ -530,6 +527,9 @@ namespace skyline::gpu {
}
std::shared_ptr<TextureView> Texture::GetView(vk::ImageViewType type, vk::ImageSubresourceRange range, texture::Format pFormat, vk::ComponentMapping mapping) {
if (!pFormat)
pFormat = format;
for (auto viewIt{views.begin()}; viewIt != views.end();) {
auto view{viewIt->lock()};
if (view && type == view->type && pFormat == view->format && range == view->range && mapping == view->mapping)