diff --git a/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h b/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h index ddd92f91..1b7c15ff 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h @@ -70,6 +70,35 @@ namespace skyline::gpu::interconnect { if (!gpu.traits.supportsLastProvokingVertex) rasterizerState.unlink(); + + // 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(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 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); diff --git a/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h b/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h index fd00bf1f..1c7c21bc 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h @@ -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, diff --git a/app/src/main/cpp/skyline/gpu/texture/texture.cpp b/app/src/main/cpp/skyline/gpu/texture/texture.cpp index 24cb1f70..6a7ea0d4 100644 --- a/app/src/main/cpp/skyline/gpu/texture/texture.cpp +++ b/app/src/main/cpp/skyline/gpu/texture/texture.cpp @@ -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 &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 &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 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)