mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
refactor cache/split responsibilities
This commit is contained in:
+1
-1
@@ -137,7 +137,7 @@ config_compiler_and_linker()
|
||||
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
||||
add_subdirectory(common)
|
||||
|
||||
file(GLOB kyty_emulator_src
|
||||
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
libs/*.cpp
|
||||
libs/*.h
|
||||
graphics/*.cpp
|
||||
|
||||
@@ -75,7 +75,7 @@ struct ImageViewInfo {
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
uint32_t swizzle = 0;
|
||||
bool is_storage = false;
|
||||
VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
bool operator==(const ImageViewInfo&) const = default;
|
||||
};
|
||||
@@ -121,15 +121,8 @@ struct VideoOutVulkanImage: public VulkanImage {
|
||||
};
|
||||
|
||||
struct DepthStencilVulkanImage: public VulkanImage {
|
||||
struct AttachmentView {
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
VkImageView view = nullptr;
|
||||
};
|
||||
DepthStencilVulkanImage(): VulkanImage(VulkanImageType::DepthStencil) {}
|
||||
bool compressed = false;
|
||||
std::mutex attachment_view_mutex;
|
||||
std::vector<AttachmentView> attachment_views;
|
||||
bool compressed = false;
|
||||
};
|
||||
|
||||
struct GpuTextureVulkanImage: public VulkanImage {
|
||||
@@ -145,17 +138,7 @@ struct StorageTextureVulkanImage: public GpuTextureVulkanImage {
|
||||
};
|
||||
|
||||
struct RenderTextureVulkanImage: public VulkanImage {
|
||||
struct AttachmentView {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t level = 0;
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
VkImageView view = nullptr;
|
||||
};
|
||||
RenderTextureVulkanImage(): VulkanImage(VulkanImageType::RenderTexture) {}
|
||||
VkImageView render_view[16] = {};
|
||||
std::mutex attachment_view_mutex;
|
||||
std::vector<AttachmentView> attachment_views;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
|
||||
@@ -676,6 +676,7 @@ std::pair<VulkanBuffer*, uint64_t> BufferCache::ObtainBuffer(CommandBuffer* com
|
||||
const auto begin = AlignDown(vaddr);
|
||||
const auto end = AlignUp(vaddr + size);
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const auto texture_region = m_texture_cache->QueryRegion(vaddr, size);
|
||||
// Use the stream-buffer fast path before image/buffer alias handling. Clean image and metadata
|
||||
// views may coexist with a small CPU-current read; Kyty's separate image trackers require
|
||||
// GPU-dirty ownership guards here. Read physical backing so
|
||||
@@ -683,8 +684,7 @@ std::pair<VulkanBuffer*, uint64_t> BufferCache::ObtainBuffer(CommandBuffer* com
|
||||
if (is_read && !is_written && size <= CACHING_PAGE_SIZE &&
|
||||
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
|
||||
m_memory_tracker.IsRegionCpuModified(vaddr, size) &&
|
||||
!m_texture_cache->HasGpuModifiedRangeOverlap(vaddr, size) &&
|
||||
!m_texture_cache->IsMetaGpuModified(vaddr, size)) {
|
||||
!texture_region.gpu_image_bytes && !texture_region.gpu_metadata_bytes) {
|
||||
std::array<uint8_t, CACHING_PAGE_SIZE> guest_data;
|
||||
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, guest_data.data(), size)) {
|
||||
VulkanBuffer* stream_buffer = nullptr;
|
||||
@@ -697,7 +697,10 @@ std::pair<VulkanBuffer*, uint64_t> BufferCache::ObtainBuffer(CommandBuffer* com
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_texture_cache->HasMetaOverlap(begin, end - begin)) {
|
||||
const auto texture_pages = begin == vaddr && end - begin == size
|
||||
? texture_region
|
||||
: m_texture_cache->QueryRegion(begin, end - begin);
|
||||
if (texture_pages.metadata_pages) {
|
||||
EXIT("BufferCache: buffer aliases metadata pages, addr=0x%016" PRIx64 " size=0x%016" PRIx64
|
||||
"\n",
|
||||
begin, end - begin);
|
||||
@@ -706,11 +709,10 @@ std::pair<VulkanBuffer*, uint64_t> BufferCache::ObtainBuffer(CommandBuffer* com
|
||||
// an edge page. Clean read-only buffer and image views may coexist; Kyty retains a hard failure
|
||||
// when either cache owns newer GPU bytes. Writable buffers delegate the ownership transition
|
||||
// to TextureCache, which distinguishes raw texture-data writes from formatted target paths.
|
||||
if (m_texture_cache->HasPageOverlap(begin, end - begin) &&
|
||||
m_texture_cache->HasRangeOverlap(vaddr, size)) {
|
||||
if (texture_pages.image_pages && texture_region.image_bytes) {
|
||||
const bool coherent_read = is_read && !is_written &&
|
||||
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
|
||||
!m_texture_cache->HasGpuModifiedRangeOverlap(vaddr, size);
|
||||
!texture_region.gpu_image_bytes;
|
||||
if (!coherent_read) {
|
||||
if (!is_written) {
|
||||
EXIT("BufferCache: unsupported buffer/image alias, addr=0x%016" PRIx64
|
||||
@@ -1084,7 +1086,8 @@ void BufferCache::FillBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
|
||||
ValidateGpuAccess(vaddr, size, false, true);
|
||||
{
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const bool image_overlap = m_texture_cache->HasRangeOverlap(vaddr, size);
|
||||
const auto texture_region = m_texture_cache->QueryRegion(vaddr, size);
|
||||
const bool image_overlap = texture_region.image_bytes;
|
||||
const bool buffer_overlap = HasPageOverlap(vaddr, size);
|
||||
const bool buffer_gpu_modified = IsRegionGpuModified(vaddr, size);
|
||||
if (!buffer_overlap && !buffer_gpu_modified) {
|
||||
@@ -1100,7 +1103,7 @@ void BufferCache::FillBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
|
||||
" size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
}
|
||||
if (m_texture_cache->HasMetaRangeOverlap(vaddr, size)) {
|
||||
if (texture_region.metadata_bytes) {
|
||||
LOGF("BufferCache: GPU fill overlaps virtual metadata, addr=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
@@ -1140,16 +1143,18 @@ void BufferCache::CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
|
||||
bool dst_image_transition = false;
|
||||
{
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const bool src_image_gpu = m_texture_cache->HasGpuModifiedRangeOverlap(src_vaddr, size);
|
||||
const bool src_meta = m_texture_cache->HasMetaRangeOverlap(src_vaddr, size);
|
||||
if (m_texture_cache->HasGpuTargetPageOverlap(src_vaddr, size)) {
|
||||
const auto src_region = m_texture_cache->QueryRegion(src_vaddr, size);
|
||||
const auto dst_region = m_texture_cache->QueryRegion(dst_vaddr, size);
|
||||
const bool src_image_gpu = src_region.gpu_image_bytes;
|
||||
const bool src_meta = src_region.metadata_bytes;
|
||||
if (src_region.non_sampled_pages) {
|
||||
EXIT("BufferCache: GPU copy aliases target pages, src=0x%016" PRIx64
|
||||
" dst=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
src_vaddr, dst_vaddr, size);
|
||||
}
|
||||
if (!HasPageOverlap(dst_vaddr, size) && !IsRegionGpuModified(src_vaddr, size) &&
|
||||
!IsRegionGpuModified(dst_vaddr, size) && !src_image_gpu) {
|
||||
if (m_texture_cache->IsMetaGpuModified(src_vaddr, size)) {
|
||||
if (src_region.gpu_metadata_bytes) {
|
||||
LOGF("BufferCache: host copy reads virtual metadata, src=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 "\n",
|
||||
src_vaddr, size);
|
||||
@@ -1157,7 +1162,7 @@ void BufferCache::CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
|
||||
"src=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
src_vaddr, size);
|
||||
}
|
||||
if (m_texture_cache->HasRangeOverlap(dst_vaddr, size)) {
|
||||
if (dst_region.image_bytes) {
|
||||
m_texture_cache->PrepareHostWrite(dst_vaddr, size);
|
||||
}
|
||||
std::memcpy(reinterpret_cast<void*>(dst_vaddr),
|
||||
@@ -1170,14 +1175,15 @@ void BufferCache::CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
|
||||
src_vaddr, dst_vaddr, size);
|
||||
}
|
||||
dst_image_transition = m_texture_cache->InvalidateMemoryFromGPU(dst_vaddr, size);
|
||||
const auto transitioned_dst = m_texture_cache->QueryRegion(dst_vaddr, size);
|
||||
// A clean target destination is handled above like a protected host write. Target
|
||||
// aliases that require an actual GPU buffer copy remain unsupported.
|
||||
if (m_texture_cache->HasGpuTargetPageOverlap(dst_vaddr, size)) {
|
||||
if (transitioned_dst.non_sampled_pages) {
|
||||
EXIT("BufferCache: GPU copy aliases target pages, src=0x%016" PRIx64
|
||||
" dst=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
src_vaddr, dst_vaddr, size);
|
||||
}
|
||||
if (src_meta || m_texture_cache->HasMetaRangeOverlap(dst_vaddr, size)) {
|
||||
if (src_meta || transitioned_dst.metadata_bytes) {
|
||||
LOGF("BufferCache: GPU copy overlaps virtual metadata, src=0x%016" PRIx64
|
||||
" dst=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
src_vaddr, dst_vaddr, size);
|
||||
|
||||
@@ -40,10 +40,10 @@ public:
|
||||
}
|
||||
return m_pool[id];
|
||||
}
|
||||
void DeleteAll();
|
||||
|
||||
private:
|
||||
void Create(int id);
|
||||
void DeleteAll();
|
||||
|
||||
VulkanCommandPool* m_pool[GraphicContext::QUEUES_NUM] = {};
|
||||
};
|
||||
@@ -78,6 +78,12 @@ void GraphicsRenderInit() {
|
||||
g_render_ctx = new RenderContext;
|
||||
}
|
||||
|
||||
void GraphicsRenderReleaseThreadCommandPools() {
|
||||
if (g_render_ctx != nullptr) {
|
||||
g_command_pool.DeleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsRenderCreateContext() {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
|
||||
|
||||
@@ -567,8 +567,8 @@ NativeTexture(uint64_t submit_id, CommandBuffer* command_buffer,
|
||||
descriptor.MsaaDepth();
|
||||
if (image == nullptr) {
|
||||
if (check_depth) {
|
||||
image =
|
||||
g_render_ctx->GetTextureCache()->FindDepthTargetByRange(address, size.size, true);
|
||||
image = g_render_ctx->GetTextureCache()->FindDepthTargetByRange(
|
||||
command_buffer, address, size.size, true);
|
||||
} else {
|
||||
image = g_render_ctx->GetTextureCache()->FindRenderTargetByRange(command_buffer,
|
||||
address, size.size);
|
||||
@@ -699,7 +699,7 @@ NativeTexture(uint64_t submit_id, CommandBuffer* command_buffer,
|
||||
}
|
||||
if (image == nullptr) {
|
||||
auto* texture_cache = g_render_ctx->GetTextureCache();
|
||||
const bool metadata_read = texture_cache->HasMetaOverlap(address, size.size);
|
||||
const bool metadata_read = texture_cache->QueryRegion(address, size.size).metadata_pages;
|
||||
if (storage && metadata_read) {
|
||||
EXIT("storage texture overlaps surface metadata\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "graphics/host_gpu/renderer/dummyTextureCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr size_t DummyTextureIndex(bool uint_format, bool image_3d) noexcept {
|
||||
return (image_3d ? 2u : 0u) + (uint_format ? 1u : 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DummyTextureCache::~DummyTextureCache() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_ctx == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
VulkanDeviceWaitIdle(m_ctx);
|
||||
const auto destroy = [this](auto& slots) {
|
||||
for (auto& slot: slots) {
|
||||
if (slot.image != nullptr) {
|
||||
ImageOps::Destroy(m_ctx, slot.image, &slot.memory);
|
||||
slot.image = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
destroy(m_sampled);
|
||||
destroy(m_storage);
|
||||
}
|
||||
|
||||
VulkanImage* DummyTextureCache::Get(GraphicContext* ctx, Usage usage, bool uint_format,
|
||||
bool image_3d) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (ctx == nullptr) {
|
||||
EXIT("TextureCache: dummy texture requires a graphics context\n");
|
||||
}
|
||||
if (m_ctx != nullptr && m_ctx != ctx) {
|
||||
EXIT("TextureCache: dummy texture context changed, previous=%p current=%p usage=%u\n",
|
||||
static_cast<const void*>(m_ctx), static_cast<const void*>(ctx),
|
||||
static_cast<uint32_t>(usage));
|
||||
}
|
||||
m_ctx = ctx;
|
||||
|
||||
auto& slots = usage == Usage::Storage ? m_storage : m_sampled;
|
||||
auto& slot = slots[DummyTextureIndex(uint_format, image_3d)];
|
||||
if (slot.image == nullptr) {
|
||||
slot.image = ImageOps::CreateDummyTexture(ctx, uint_format, image_3d,
|
||||
usage == Usage::Storage, &slot.memory);
|
||||
}
|
||||
return slot.image;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class DummyTextureCache final {
|
||||
public:
|
||||
enum class Usage: uint8_t { Sampled, Storage };
|
||||
|
||||
DummyTextureCache() = default;
|
||||
~DummyTextureCache();
|
||||
KYTY_CLASS_NO_COPY(DummyTextureCache);
|
||||
|
||||
[[nodiscard]] VulkanImage* Get(GraphicContext* ctx, Usage usage, bool uint_format,
|
||||
bool image_3d);
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
GpuTextureVulkanImage* image = nullptr;
|
||||
VulkanMemory memory {};
|
||||
};
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
std::array<Slot, 4> m_sampled {};
|
||||
std::array<Slot, 4> m_storage {};
|
||||
GraphicContext* m_ctx = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
|
||||
@@ -0,0 +1,470 @@
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
TextureImageCreateParams MakeImageParams(const ImageInfo& info, bool storage) {
|
||||
TextureImageCreateParams params {};
|
||||
params.fmt = info.format;
|
||||
params.width = info.width;
|
||||
params.height = info.height;
|
||||
params.base_level = SelectImageBackingBaseLevel(storage, info.base_level);
|
||||
params.levels = info.levels;
|
||||
params.depth = info.depth;
|
||||
params.type = info.type;
|
||||
// Storage image views use identity component mapping. The guest storage write mapping is
|
||||
// validated before this point and intentionally does not become a Vulkan view swizzle.
|
||||
params.swizzle = storage ? DstSel(4, 5, 6, 7) : info.swizzle;
|
||||
params.format_usage = TextureFormatUsage::Sampled | TextureFormatUsage::Storage;
|
||||
params.required_format_usage = storage
|
||||
? TextureFormatUsage::Sampled | TextureFormatUsage::Storage
|
||||
: TextureFormatUsage::Sampled;
|
||||
params.view_usage = storage ? TextureFormatUsage::Sampled | TextureFormatUsage::Storage
|
||||
: TextureFormatUsage::Sampled;
|
||||
params.image_layout = TextureUploadDestination::MipLevels;
|
||||
params.allow_cube_view = !storage;
|
||||
params.compatible_format_views =
|
||||
storage &&
|
||||
(IsRgba8SrgbViewFormat(TextureGetFormat(info.format, params.format_usage)) ||
|
||||
info.format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) ||
|
||||
info.format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float));
|
||||
params.owner = storage ? "StorageTextureCache" : "TextureCache";
|
||||
return params;
|
||||
}
|
||||
|
||||
bool RenderTargetSupportsStorage(GraphicContext* ctx, VkFormat format, VkImageCreateFlags flags) {
|
||||
const auto compatible = SrgbStorageViewFormat(format);
|
||||
const auto required_flags =
|
||||
VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
|
||||
const bool compatible_views = (flags & required_flags) == required_flags;
|
||||
return ImageViewOps::FormatSupportsStorage(ctx, format) ||
|
||||
(compatible_views && compatible != VK_FORMAT_UNDEFINED &&
|
||||
ImageViewOps::FormatSupportsStorage(ctx, compatible));
|
||||
}
|
||||
|
||||
VkImageCreateFlags RenderTargetCreateFlags(VkFormat format) {
|
||||
const bool compatible_format_view =
|
||||
IsRgba8SrgbViewFormat(format) || BgraToRgbaSampledViewFormat(format) != VK_FORMAT_UNDEFINED ||
|
||||
format == VK_FORMAT_R8G8B8A8_UINT || format == VK_FORMAT_R16G16B16A16_SFLOAT ||
|
||||
format == VK_FORMAT_R16G16B16A16_UINT;
|
||||
return compatible_format_view
|
||||
? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT
|
||||
: VkImageCreateFlags {0};
|
||||
}
|
||||
|
||||
VkImageUsageFlags RenderTargetUsage(GraphicContext* ctx, VkFormat format,
|
||||
VkImageCreateFlags flags) {
|
||||
auto usage = static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) |
|
||||
static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_TRANSFER_SRC_BIT) |
|
||||
static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_TRANSFER_DST_BIT) |
|
||||
static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
if (RenderTargetSupportsStorage(ctx, format, flags)) {
|
||||
usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
}
|
||||
VkImageFormatProperties properties {};
|
||||
if (ctx->GetImageFormatProperties(format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage,
|
||||
flags, &properties) != VK_SUCCESS) {
|
||||
EXIT("TextureCache: render-target format does not support required usage, format=%d "
|
||||
"usage=0x%x\n",
|
||||
static_cast<int>(format), usage);
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t RenderTargetTransferFormatImpl(uint32_t bytes_per_element) {
|
||||
switch (bytes_per_element) {
|
||||
case 1: return Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm);
|
||||
case 2: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
|
||||
case 4: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
|
||||
case 8: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float);
|
||||
case 16: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float);
|
||||
default:
|
||||
EXIT("TextureCache: unsupported render-target element size: %u\n", bytes_per_element);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t DummyTextureSwizzle() {
|
||||
return Prospero::GpuEnumValue(Prospero::CompSwizzle::kRed) |
|
||||
(Prospero::GpuEnumValue(Prospero::CompSwizzle::kGreen) << 3u) |
|
||||
(Prospero::GpuEnumValue(Prospero::CompSwizzle::kBlue) << 6u) |
|
||||
(Prospero::GpuEnumValue(Prospero::CompSwizzle::kAlpha) << 9u);
|
||||
}
|
||||
|
||||
TextureImageCreateParams MakeDummyTextureParams(bool uint_format, bool image_3d,
|
||||
TextureFormatUsage usage,
|
||||
const char* owner) {
|
||||
TextureImageCreateParams params {};
|
||||
params.fmt = static_cast<uint32_t>(
|
||||
Prospero::GpuEnumValue(uint_format ? Prospero::BufferFormat::k8_8_8_8UInt
|
||||
: Prospero::BufferFormat::k8_8_8_8UNorm));
|
||||
params.width = 1;
|
||||
params.height = 1;
|
||||
params.base_level = 0;
|
||||
params.levels = 1;
|
||||
params.depth = 1;
|
||||
params.type = Prospero::GpuEnumValue(image_3d ? Prospero::ImageType::kColor3D
|
||||
: Prospero::ImageType::kColor2D);
|
||||
params.swizzle = DummyTextureSwizzle();
|
||||
params.format_usage = usage;
|
||||
params.required_format_usage = usage;
|
||||
params.view_usage = usage;
|
||||
params.image_layout = TextureUploadDestination::MipLevels;
|
||||
params.allow_cube_view = true;
|
||||
params.storage_swizzle_fallback = TextureHasFormatUsage(usage, TextureFormatUsage::Storage);
|
||||
params.owner = owner;
|
||||
return params;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ImageOps {
|
||||
|
||||
uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
|
||||
return RenderTargetTransferFormatImpl(bytes_per_element);
|
||||
}
|
||||
|
||||
GpuTextureVulkanImage* CreateTexture(GraphicContext* ctx, const ImageInfo& info, bool storage,
|
||||
VulkanMemory* memory, VkComponentMapping* components) {
|
||||
if (components == nullptr) {
|
||||
EXIT("TextureCache: invalid texture component output\n");
|
||||
}
|
||||
auto* image = storage ? static_cast<GpuTextureVulkanImage*>(new StorageTextureVulkanImage)
|
||||
: new TextureVulkanImage;
|
||||
*components = TextureCreateImage(ctx, image, memory, MakeImageParams(info, storage));
|
||||
return image;
|
||||
}
|
||||
|
||||
void CreateTextureViews(GraphicContext* ctx, GpuTextureVulkanImage* image, const ImageInfo& info,
|
||||
bool storage, VkComponentMapping components) {
|
||||
if (storage) {
|
||||
TextureCreateImageViews(ctx, image, components, info.type, 0, 0, 1, info.depth, false,
|
||||
TextureFormatUsage::Sampled | TextureFormatUsage::Storage);
|
||||
} else {
|
||||
TextureCreateImageViews(ctx, image, components, info.type, info.base_array, info.base_level,
|
||||
info.view_levels, info.depth, true, TextureFormatUsage::Sampled);
|
||||
}
|
||||
}
|
||||
|
||||
void UploadRenderTargetLayers(GraphicContext* ctx, RenderTextureVulkanImage* image,
|
||||
const RenderTargetInfo& info, uint32_t base_layer,
|
||||
uint32_t layer_count, bool refresh) {
|
||||
if (info.layers == 0 || info.size % info.layers != 0 || layer_count == 0 ||
|
||||
base_layer >= info.layers || layer_count > info.layers - base_layer || image == nullptr ||
|
||||
base_layer >= image->layers || layer_count > image->layers - base_layer) {
|
||||
EXIT("TextureCache: invalid render-target layer upload, base=%u count=%u "
|
||||
"info_layers=%u image_layers=%u size=0x%016" PRIx64 "\n",
|
||||
base_layer, layer_count, info.layers, image != nullptr ? image->layers : 0, info.size);
|
||||
}
|
||||
if (refresh) {
|
||||
VulkanDeviceWaitIdle(ctx);
|
||||
}
|
||||
const auto slice_size = info.size / info.layers;
|
||||
const auto upload_size = slice_size * layer_count;
|
||||
const bool standard64 = IsSupportedStandard64RenderTarget(info);
|
||||
if (standard64 || info.levels > 1 || info.layers > 1) {
|
||||
const auto format = RenderTargetTransferFormat(info.bytes_per_element);
|
||||
auto layout = TextureCalcUploadLayout(format, info.width, info.height, info.levels,
|
||||
layer_count, info.pitch, info.tile_mode, upload_size,
|
||||
false, false, false, "TextureCache render target");
|
||||
const bool render_target_tiled =
|
||||
info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
|
||||
if (!standard64 && ((render_target_tiled && !layout.fmt_tiled_render_target) ||
|
||||
layout.pitch != info.pitch)) {
|
||||
EXIT("TextureCache: unsupported render-target mip upload layout, pitch=%u/%u tile=%u\n",
|
||||
info.pitch, layout.pitch, info.tile_mode);
|
||||
}
|
||||
auto regions = TextureBuildUploadRegions(
|
||||
layout, info.format, info.width, info.height, layer_count, info.levels, true, false,
|
||||
TextureUploadDestination::MipLevels, TextureUploadSliceLayout::MipChainPerSlice);
|
||||
for (auto& region: regions) {
|
||||
region.dst_layer += base_layer;
|
||||
}
|
||||
const auto source_address = info.address + slice_size * base_layer;
|
||||
TextureUploadGuestImage(
|
||||
ctx, image, reinterpret_cast<const void*>(source_address), upload_size, regions, layout,
|
||||
format, info.width, info.height, layer_count, info.levels,
|
||||
TextureUploadSliceLayout::MipChainPerSlice, "TextureCache render target",
|
||||
static_cast<uint64_t>(VK_IMAGE_LAYOUT_GENERAL));
|
||||
return;
|
||||
}
|
||||
if (info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) &&
|
||||
UtilBufferIsTiled(info.address, slice_size)) {
|
||||
UtilScratchBuffer scratch(slice_size);
|
||||
TileConvertTiledToLinearRenderTarget(
|
||||
scratch.Data(), reinterpret_cast<const void*>(info.address), info.width, info.height,
|
||||
info.pitch, info.bytes_per_element, slice_size);
|
||||
UtilFillImage(ctx, image, scratch.Data(), slice_size, info.pitch,
|
||||
static_cast<uint64_t>(VK_IMAGE_LAYOUT_GENERAL));
|
||||
} else {
|
||||
UtilFillImage(ctx, image, reinterpret_cast<const void*>(info.address), slice_size,
|
||||
info.pitch, static_cast<uint64_t>(VK_IMAGE_LAYOUT_GENERAL));
|
||||
}
|
||||
}
|
||||
|
||||
void UploadRenderTarget(GraphicContext* ctx, RenderTextureVulkanImage* image,
|
||||
const RenderTargetInfo& info, bool refresh) {
|
||||
UploadRenderTargetLayers(ctx, image, info, 0, info.layers, refresh);
|
||||
}
|
||||
|
||||
RenderTextureVulkanImage* CreateRenderTarget(GraphicContext* ctx, const RenderTargetInfo& info,
|
||||
VulkanMemory* memory) {
|
||||
auto* image = new RenderTextureVulkanImage;
|
||||
image->extent.width = info.width;
|
||||
image->extent.height = info.height;
|
||||
image->format = info.format;
|
||||
image->mip_levels = info.levels;
|
||||
image->layers = info.layers;
|
||||
image->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
UtilResetImageViews(image);
|
||||
VkImageCreateInfo create {};
|
||||
create.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
create.flags = RenderTargetCreateFlags(info.format);
|
||||
create.imageType = VK_IMAGE_TYPE_2D;
|
||||
create.extent = {info.width, info.height, 1};
|
||||
create.mipLevels = info.levels;
|
||||
create.arrayLayers = info.layers;
|
||||
create.format = info.format;
|
||||
create.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
create.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
create.usage = RenderTargetUsage(ctx, info.format, create.flags);
|
||||
create.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
create.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
memory->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
if (!VulkanCreateImage(ctx, &create, image, memory)) {
|
||||
EXIT("TextureCache: failed to create render target, addr=0x%016" PRIx64
|
||||
" extent=%ux%u format=%d\n",
|
||||
info.address, info.width, info.height, static_cast<int>(info.format));
|
||||
}
|
||||
image->memory = *memory;
|
||||
ImageViewOps::CreateRenderTargetViews(ctx, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
DepthStencilVulkanImage* CreateDepthTarget(GraphicContext* ctx, const DepthTargetInfo& info,
|
||||
VulkanMemory* memory) {
|
||||
VkImageCreateInfo create {};
|
||||
create.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
create.imageType = VK_IMAGE_TYPE_2D;
|
||||
create.extent = {info.width, info.height, 1};
|
||||
create.mipLevels = 1;
|
||||
create.arrayLayers = info.layers;
|
||||
create.format = info.format;
|
||||
create.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
create.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
create.usage = DepthTargetImageUsage();
|
||||
create.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
create.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImageFormatProperties properties {};
|
||||
if (ctx->GetImageFormatProperties(info.format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
|
||||
create.usage, 0, &properties) != VK_SUCCESS) {
|
||||
EXIT("TextureCache: depth format does not support required usage, format=%d usage=0x%x\n",
|
||||
static_cast<int>(info.format), create.usage);
|
||||
}
|
||||
auto* image = new DepthStencilVulkanImage;
|
||||
image->extent.width = info.width;
|
||||
image->extent.height = info.height;
|
||||
image->guest_pitch = info.pitch;
|
||||
image->layers = info.layers;
|
||||
image->format = info.format;
|
||||
image->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image->compressed = false;
|
||||
UtilResetImageViews(image);
|
||||
memory->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
if (!VulkanCreateImage(ctx, &create, image, memory)) {
|
||||
EXIT("TextureCache: failed to create depth target, addr=0x%016" PRIx64
|
||||
" extent=%ux%u format=%d\n",
|
||||
info.address, info.width, info.height, static_cast<int>(info.format));
|
||||
}
|
||||
image->memory = *memory;
|
||||
ImageViewOps::CreateDepthViews(ctx, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
void ValidateVideoOut(GraphicContext* ctx, const VideoOutInfo& info) {
|
||||
const auto compression =
|
||||
ClassifyVideoOutCompression(info.compression != VideoOutCompression::Uncompressed,
|
||||
info.metadata_address, info.dcc_control, 0);
|
||||
const bool metadata_invalid = compression == VideoOutCompression::Dcc256_64_64 &&
|
||||
(info.metadata_address >= TRACKER_ADDRESS_SIZE ||
|
||||
(info.metadata_address >= info.address &&
|
||||
info.metadata_address < info.address + info.size));
|
||||
if (ctx == nullptr || info.address == 0 || info.size == 0 ||
|
||||
info.address >= TRACKER_ADDRESS_SIZE || info.size > TRACKER_ADDRESS_SIZE - info.address ||
|
||||
(info.address & 0xffffu) != 0 || info.width == 0 || info.height == 0 ||
|
||||
info.width > 16384 || info.height > 16384 || info.pitch < info.width ||
|
||||
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
|
||||
compression == VideoOutCompression::Unsupported || compression != info.compression ||
|
||||
metadata_invalid || !IsSupportedVideoOutFormat(info)) {
|
||||
EXIT("TextureCache: unsupported video-out surface, ctx=%p addr=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " metadata=0x%016" PRIx64 " dcc=0x%08" PRIx32
|
||||
" extent=%ux%u pitch=%u tile=%u guest_format=%u bpe=%u vk_format=%d\n",
|
||||
static_cast<const void*>(ctx), info.address, info.size, info.metadata_address,
|
||||
info.dcc_control, info.width, info.height, info.pitch, info.tile_mode,
|
||||
info.guest_format, info.bytes_per_element, static_cast<int>(info.format));
|
||||
}
|
||||
TileSizeAlign exact {};
|
||||
TileGetTextureTotalSize(info.guest_format, info.width, info.height, 1, info.pitch, 1,
|
||||
info.tile_mode, false, &exact);
|
||||
if (exact.align != 65536 || exact.size != info.size ||
|
||||
TileGetTexturePitch(info.guest_format, info.width, 1, info.tile_mode) != info.pitch) {
|
||||
EXIT("TextureCache: video-out tile layout mismatch, addr=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " expected_size=0x%016" PRIx64 " align=0x%016" PRIx64
|
||||
" pitch=%u\n",
|
||||
info.address, info.size, exact.size, exact.align, info.pitch);
|
||||
}
|
||||
(void)RenderTargetUsage(ctx, info.format, 0);
|
||||
}
|
||||
|
||||
VideoOutVulkanImage* CreateVideoOut(GraphicContext* ctx, const VideoOutInfo& info,
|
||||
VulkanMemory* memory) {
|
||||
auto* image = new VideoOutVulkanImage;
|
||||
image->extent.width = info.width;
|
||||
image->extent.height = info.height;
|
||||
image->format = info.format;
|
||||
image->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
UtilResetImageViews(image);
|
||||
VkImageCreateInfo create {};
|
||||
create.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
create.imageType = VK_IMAGE_TYPE_2D;
|
||||
create.extent = {info.width, info.height, 1};
|
||||
create.mipLevels = 1;
|
||||
create.arrayLayers = 1;
|
||||
create.format = info.format;
|
||||
create.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
create.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
create.flags = RenderTargetCreateFlags(info.format);
|
||||
create.usage = RenderTargetUsage(ctx, info.format, create.flags);
|
||||
create.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
create.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
memory->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
if (!VulkanCreateImage(ctx, &create, image, memory)) {
|
||||
EXIT("TextureCache: failed to create video-out image, addr=0x%016" PRIx64
|
||||
" extent=%ux%u format=%d\n",
|
||||
info.address, info.width, info.height, static_cast<int>(info.format));
|
||||
}
|
||||
image->memory = *memory;
|
||||
ImageViewOps::CreateVideoOutViews(ctx, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
void UploadVideoOut(GraphicContext* ctx, VideoOutVulkanImage* image, const VideoOutInfo& info,
|
||||
bool refresh) {
|
||||
if (info.compression != VideoOutCompression::Uncompressed) {
|
||||
EXIT("TextureCache: compressed video-out guest upload is unsupported, "
|
||||
"addr=0x%016" PRIx64 " metadata=0x%016" PRIx64 " dcc=0x%08" PRIx32 "\n",
|
||||
info.address, info.metadata_address, info.dcc_control);
|
||||
}
|
||||
if (refresh) {
|
||||
VulkanDeviceWaitIdle(ctx);
|
||||
}
|
||||
image->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
UtilScratchBuffer scratch(info.size);
|
||||
TileConvertTiledToLinearRenderTarget(
|
||||
scratch.Data(), reinterpret_cast<const void*>(info.address), info.width, info.height,
|
||||
info.pitch, info.bytes_per_element, info.size);
|
||||
if (info.bgra16) {
|
||||
auto* pixels = static_cast<uint16_t*>(scratch.Data());
|
||||
for (uint64_t i = 0; i < info.size / sizeof(uint16_t); i += 4) {
|
||||
std::swap(pixels[i], pixels[i + 2]);
|
||||
}
|
||||
}
|
||||
UtilFillImage(ctx, image, scratch.Data(), info.size, info.pitch,
|
||||
static_cast<uint64_t>(VK_IMAGE_LAYOUT_GENERAL));
|
||||
}
|
||||
|
||||
GpuTextureVulkanImage* CreateDummyTexture(GraphicContext* ctx, bool uint_format, bool image_3d,
|
||||
bool storage, VulkanMemory* memory) {
|
||||
if (memory == nullptr || memory->allocation != nullptr) {
|
||||
EXIT("TextureCache: invalid dummy texture memory slot, slot=%p allocation=%p storage=%d\n",
|
||||
static_cast<const void*>(memory),
|
||||
memory == nullptr ? nullptr : static_cast<const void*>(memory->allocation), storage);
|
||||
}
|
||||
auto* image = storage ? static_cast<GpuTextureVulkanImage*>(new StorageTextureVulkanImage)
|
||||
: new TextureVulkanImage;
|
||||
auto usage = storage ? TextureFormatUsage::Storage : TextureFormatUsage::Sampled;
|
||||
auto layout = storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
auto owner = storage ? "DummyStorageTexture" : "DummySampledTexture";
|
||||
|
||||
auto params = MakeDummyTextureParams(uint_format, image_3d, usage, owner);
|
||||
auto components = TextureCreateImage(ctx, image, memory, params);
|
||||
|
||||
static constexpr uint32_t zero = 0;
|
||||
UtilFillImage(ctx, image, &zero, sizeof(zero), 1, static_cast<uint64_t>(layout));
|
||||
TextureCreateImageViews(ctx, image, components, params.type, 0, params.base_level,
|
||||
params.levels, params.depth, params.allow_cube_view, params.view_usage);
|
||||
return image;
|
||||
}
|
||||
|
||||
void Destroy(GraphicContext* ctx, GpuTextureVulkanImage* image, VulkanMemory* memory) {
|
||||
KYTY_PROFILER_BLOCK("TextureCache::DeleteGpuTexture");
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
ImageViewOps::DestroyViews(ctx, image);
|
||||
VulkanDeleteImage(ctx, image, memory);
|
||||
|
||||
switch (image->type) {
|
||||
case VulkanImageType::Texture: delete static_cast<TextureVulkanImage*>(image); break;
|
||||
case VulkanImageType::StorageTexture:
|
||||
delete static_cast<StorageTextureVulkanImage*>(image);
|
||||
break;
|
||||
default: EXIT("unsupported gpu texture image type: %d\n", static_cast<int>(image->type));
|
||||
}
|
||||
}
|
||||
|
||||
void Destroy(GraphicContext* ctx, RenderTextureVulkanImage* image, VulkanMemory* memory) {
|
||||
KYTY_PROFILER_BLOCK("TextureCache::DeleteRenderTexture");
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
g_render_ctx->GetFramebufferCache()->FreeFramebufferByColor(image);
|
||||
ImageViewOps::DestroyViews(ctx, image);
|
||||
VulkanDeleteImage(ctx, image, memory);
|
||||
delete image;
|
||||
}
|
||||
|
||||
void Destroy(GraphicContext* ctx, DepthStencilVulkanImage* image, VulkanMemory* memory) {
|
||||
KYTY_PROFILER_BLOCK("TextureCache::DeleteDepthStencil");
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
g_render_ctx->GetFramebufferCache()->FreeFramebufferByDepth(image);
|
||||
ImageViewOps::DestroyViews(ctx, image);
|
||||
VulkanDeleteImage(ctx, image, memory);
|
||||
delete image;
|
||||
}
|
||||
|
||||
void Destroy(GraphicContext* ctx, VideoOutVulkanImage* image, VulkanMemory* memory) {
|
||||
KYTY_PROFILER_BLOCK("TextureCache::DeleteVideoOut");
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
g_render_ctx->GetFramebufferCache()->FreeFramebufferByColor(image);
|
||||
ImageViewOps::DestroyViews(ctx, image);
|
||||
VulkanDeleteImage(ctx, image, memory);
|
||||
delete image;
|
||||
}
|
||||
|
||||
} // namespace ImageOps
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -10,6 +10,13 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct DepthStencilVulkanImage;
|
||||
struct GpuTextureVulkanImage;
|
||||
struct GraphicContext;
|
||||
struct RenderTextureVulkanImage;
|
||||
struct VideoOutVulkanImage;
|
||||
struct VulkanMemory;
|
||||
|
||||
struct Image final: ImageInfo {
|
||||
Image& operator=(const ImageInfo& value) {
|
||||
if (IsCpuDirty()) {
|
||||
@@ -91,6 +98,43 @@ private:
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
};
|
||||
|
||||
namespace ImageOps {
|
||||
|
||||
[[nodiscard]] GpuTextureVulkanImage*
|
||||
CreateTexture(GraphicContext* ctx, const ImageInfo& info, bool storage, VulkanMemory* memory,
|
||||
VkComponentMapping* components);
|
||||
void CreateTextureViews(GraphicContext* ctx, GpuTextureVulkanImage* image, const ImageInfo& info,
|
||||
bool storage, VkComponentMapping components);
|
||||
|
||||
[[nodiscard]] RenderTextureVulkanImage*
|
||||
CreateRenderTarget(GraphicContext* ctx, const RenderTargetInfo& info, VulkanMemory* memory);
|
||||
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
|
||||
void UploadRenderTargetLayers(GraphicContext* ctx, RenderTextureVulkanImage* image,
|
||||
const RenderTargetInfo& info, uint32_t base_layer,
|
||||
uint32_t layer_count, bool refresh);
|
||||
void UploadRenderTarget(GraphicContext* ctx, RenderTextureVulkanImage* image,
|
||||
const RenderTargetInfo& info, bool refresh);
|
||||
|
||||
[[nodiscard]] DepthStencilVulkanImage*
|
||||
CreateDepthTarget(GraphicContext* ctx, const DepthTargetInfo& info, VulkanMemory* memory);
|
||||
|
||||
void ValidateVideoOut(GraphicContext* ctx, const VideoOutInfo& info);
|
||||
[[nodiscard]] VideoOutVulkanImage*
|
||||
CreateVideoOut(GraphicContext* ctx, const VideoOutInfo& info, VulkanMemory* memory);
|
||||
void UploadVideoOut(GraphicContext* ctx, VideoOutVulkanImage* image, const VideoOutInfo& info,
|
||||
bool refresh);
|
||||
|
||||
[[nodiscard]] GpuTextureVulkanImage*
|
||||
CreateDummyTexture(GraphicContext* ctx, bool uint_format, bool image_3d, bool storage,
|
||||
VulkanMemory* memory);
|
||||
|
||||
void Destroy(GraphicContext* ctx, GpuTextureVulkanImage* image, VulkanMemory* memory);
|
||||
void Destroy(GraphicContext* ctx, RenderTextureVulkanImage* image, VulkanMemory* memory);
|
||||
void Destroy(GraphicContext* ctx, DepthStencilVulkanImage* image, VulkanMemory* memory);
|
||||
void Destroy(GraphicContext* ctx, VideoOutVulkanImage* image, VulkanMemory* memory);
|
||||
|
||||
} // namespace ImageOps
|
||||
|
||||
struct ImageRetirementRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/textureCache.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
void CreateRenderTargetView(GraphicContext* ctx, VulkanImage* image, int index,
|
||||
VkComponentSwizzle r, VkComponentSwizzle g, VkComponentSwizzle b,
|
||||
VkComponentSwizzle a, VkImageViewType type = VK_IMAGE_VIEW_TYPE_2D,
|
||||
VkFormat view_format = VK_FORMAT_UNDEFINED,
|
||||
VkImageUsageFlags view_usage = 0, uint32_t level_count = 0) {
|
||||
const auto layer_count = type == VK_IMAGE_VIEW_TYPE_2D_ARRAY ? image->layers : 1u;
|
||||
UtilCreateImageView(ctx, image, index, type, VK_IMAGE_ASPECT_COLOR_BIT, {r, g, b, a}, 0, 0,
|
||||
layer_count, level_count == 0 ? image->mip_levels : level_count,
|
||||
view_format, view_usage);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ImageViewOps {
|
||||
|
||||
VkImageAspectFlags DepthAspectMask(VkFormat format) noexcept {
|
||||
return VK_IMAGE_ASPECT_DEPTH_BIT |
|
||||
(format == VK_FORMAT_D16_UNORM_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
format == VK_FORMAT_D32_SFLOAT_S8_UINT
|
||||
? VK_IMAGE_ASPECT_STENCIL_BIT
|
||||
: 0u);
|
||||
}
|
||||
|
||||
bool FormatSupportsStorage(GraphicContext* ctx, VkFormat format) {
|
||||
const auto properties = ctx->GetFormatProperties(format);
|
||||
return (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||
}
|
||||
|
||||
void CreateRenderTargetViews(GraphicContext* ctx, RenderTextureVulkanImage* image) {
|
||||
CreateRenderTargetView(ctx, image, VulkanImage::VIEW_DEFAULT, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY);
|
||||
if (image->layers > 1) {
|
||||
CreateRenderTargetView(ctx, image, VulkanImage::VIEW_DEFAULT_ARRAY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_IMAGE_VIEW_TYPE_2D_ARRAY);
|
||||
}
|
||||
if (FormatSupportsStorage(ctx, image->format)) {
|
||||
CreateRenderTargetView(ctx, image, VulkanImage::VIEW_STORAGE, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_IMAGE_VIEW_TYPE_2D,
|
||||
VK_FORMAT_UNDEFINED, 0, 1);
|
||||
if (image->layers > 1) {
|
||||
CreateRenderTargetView(ctx, image, VulkanImage::VIEW_STORAGE_ARRAY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_FORMAT_UNDEFINED, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDepthViews(GraphicContext* ctx, DepthStencilVulkanImage* image) {
|
||||
UtilCreateImageView(ctx, image, VulkanImage::VIEW_DEFAULT, VK_IMAGE_VIEW_TYPE_2D,
|
||||
DepthAspectMask(image->format),
|
||||
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
|
||||
0, 0, 1, 1);
|
||||
}
|
||||
|
||||
void CreateVideoOutViews(GraphicContext* ctx, VideoOutVulkanImage* image) {
|
||||
CreateRenderTargetView(ctx, image, VulkanImage::VIEW_DEFAULT, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY);
|
||||
if ((image->format == VK_FORMAT_R8G8B8A8_SRGB || image->format == VK_FORMAT_B8G8R8A8_SRGB) &&
|
||||
FormatSupportsStorage(ctx, VK_FORMAT_R8G8B8A8_UINT)) {
|
||||
CreateRenderTargetView(ctx, image, VulkanImage::VIEW_STORAGE, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_IMAGE_VIEW_TYPE_2D,
|
||||
VK_FORMAT_R8G8B8A8_UINT, VK_IMAGE_USAGE_STORAGE_BIT, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyViews(GraphicContext* ctx, VulkanImage* image) {
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
for (auto& cached: image->view_cache.views) {
|
||||
if (cached.view != nullptr) {
|
||||
vkDestroyImageView(ctx->device, cached.view, nullptr);
|
||||
cached.view = nullptr;
|
||||
}
|
||||
}
|
||||
image->view_cache.views.clear();
|
||||
for (auto& view: image->image_view) {
|
||||
if (view != nullptr) {
|
||||
vkDestroyImageView(ctx->device, view, nullptr);
|
||||
view = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ImageViewOps
|
||||
|
||||
VkImageView TextureCache::GetRenderTargetAttachmentView(GraphicContext* ctx,
|
||||
RenderTextureVulkanImage* image,
|
||||
VkFormat format, uint32_t level,
|
||||
uint32_t base_layer, uint32_t layer_count) {
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
format == VK_FORMAT_UNDEFINED || level >= image->mip_levels || level >= 16 ||
|
||||
layer_count == 0 || base_layer >= image->layers ||
|
||||
layer_count > image->layers - base_layer) {
|
||||
EXIT("TextureCache: invalid render-target attachment view, image=%p format=%d"
|
||||
" level=%u image_levels=%u base_layer=%u layer_count=%u image_layers=%u\n",
|
||||
static_cast<const void*>(image), static_cast<int>(format), level,
|
||||
image != nullptr ? image->mip_levels : 0, base_layer, layer_count,
|
||||
image != nullptr ? image->layers : 0);
|
||||
}
|
||||
if (format != image->format && !IsRgba8SrgbReinterpretation(image->format, format)) {
|
||||
EXIT("TextureCache: incompatible render-target attachment view, image_format=%d"
|
||||
" view_format=%d level=%u\n",
|
||||
static_cast<int>(image->format), static_cast<int>(format), level);
|
||||
}
|
||||
|
||||
return GetImageView(
|
||||
ctx, image,
|
||||
{format, layer_count == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT, level, 1, base_layer, layer_count, DstSel(4, 5, 6, 7),
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT});
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetDepthTargetAttachmentView(GraphicContext* ctx,
|
||||
DepthStencilVulkanImage* image,
|
||||
uint32_t base_layer, uint32_t layer_count) {
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr || layer_count == 0 ||
|
||||
base_layer >= image->layers || layer_count > image->layers - base_layer) {
|
||||
EXIT("TextureCache: invalid depth-target attachment view, image=%p base_layer=%u "
|
||||
"layer_count=%u image_layers=%u\n",
|
||||
static_cast<const void*>(image), base_layer, layer_count,
|
||||
image != nullptr ? image->layers : 0);
|
||||
}
|
||||
return GetImageView(
|
||||
ctx, image,
|
||||
{image->format, layer_count == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
||||
ImageViewOps::DepthAspectMask(image->format), 0, 1, base_layer, layer_count,
|
||||
DstSel(4, 5, 6, 7), VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT});
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetImageView(GraphicContext* ctx, VulkanImage* image,
|
||||
const ImageViewInfo& info) {
|
||||
const bool supported_type = info.type == VK_IMAGE_VIEW_TYPE_2D ||
|
||||
info.type == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
|
||||
info.type == VK_IMAGE_VIEW_TYPE_3D;
|
||||
const bool supported_usage = info.usage == VK_IMAGE_USAGE_SAMPLED_BIT ||
|
||||
info.usage == VK_IMAGE_USAGE_STORAGE_BIT ||
|
||||
info.usage == VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT ||
|
||||
info.usage == VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
const bool valid_shape =
|
||||
(info.type == VK_IMAGE_VIEW_TYPE_2D && info.layer_count == 1) ||
|
||||
info.type == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
|
||||
(info.type == VK_IMAGE_VIEW_TYPE_3D && info.base_layer == 0 && info.layer_count == 1);
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
info.format == VK_FORMAT_UNDEFINED || info.aspect == 0 || info.level_count == 0 ||
|
||||
info.base_level >= (image != nullptr ? image->mip_levels : 0) ||
|
||||
info.level_count > image->mip_levels - info.base_level || info.layer_count == 0 ||
|
||||
info.base_layer >= image->layers || info.layer_count > image->layers - info.base_layer ||
|
||||
!supported_type || !valid_shape || !supported_usage) {
|
||||
EXIT("TextureCache: invalid dynamic image view, image=%p format=%d aspect=0x%x"
|
||||
" swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x"
|
||||
" image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(image), static_cast<int>(info.format), info.aspect,
|
||||
info.swizzle, info.base_level, info.level_count, info.base_layer, info.layer_count,
|
||||
static_cast<int>(info.type), info.usage, image != nullptr ? image->mip_levels : 0,
|
||||
image != nullptr ? image->layers : 0);
|
||||
}
|
||||
|
||||
auto& cache = image->view_cache;
|
||||
std::lock_guard lock(cache.mutex);
|
||||
for (const auto& cached: cache.views) {
|
||||
if (cached.info == info) {
|
||||
return cached.view;
|
||||
}
|
||||
}
|
||||
|
||||
VkImageViewUsageCreateInfo usage {};
|
||||
usage.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
|
||||
usage.usage = info.usage;
|
||||
VkImageViewCreateInfo create {};
|
||||
create.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
create.pNext = &usage;
|
||||
create.image = image->image;
|
||||
create.viewType = info.type;
|
||||
create.format = info.format;
|
||||
create.components = info.usage == VK_IMAGE_USAGE_SAMPLED_BIT
|
||||
? TextureGetComponentMapping(info.swizzle)
|
||||
: VkComponentMapping {};
|
||||
create.subresourceRange.aspectMask = info.aspect;
|
||||
create.subresourceRange.baseMipLevel = info.base_level;
|
||||
create.subresourceRange.levelCount = info.level_count;
|
||||
create.subresourceRange.baseArrayLayer = info.base_layer;
|
||||
create.subresourceRange.layerCount = info.layer_count;
|
||||
VkImageView view = nullptr;
|
||||
const auto result = vkCreateImageView(ctx->device, &create, nullptr, &view);
|
||||
if (result != VK_SUCCESS || view == nullptr) {
|
||||
EXIT("TextureCache: failed to create dynamic image view, result=%d format=%d"
|
||||
" aspect=0x%x swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x\n",
|
||||
static_cast<int>(result), static_cast<int>(info.format), info.aspect, info.swizzle,
|
||||
info.base_level, info.level_count, info.base_layer, info.layer_count,
|
||||
static_cast<int>(info.type), info.usage);
|
||||
}
|
||||
cache.views.push_back({info, view});
|
||||
return view;
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetDepthTargetSampledView(GraphicContext* ctx,
|
||||
DepthStencilVulkanImage* image,
|
||||
VkFormat view_format, uint32_t swizzle,
|
||||
uint32_t base_level, uint32_t level_count,
|
||||
VkImageViewType type, uint32_t base_layer,
|
||||
uint32_t layer_count) {
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
view_format == VK_FORMAT_UNDEFINED ||
|
||||
!IsSupportedSampledDepthView(image->format, view_format, swizzle)) {
|
||||
EXIT("TextureCache: invalid sampled depth-target view, image=%p image_format=%d"
|
||||
" view_format=%d swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d"
|
||||
" image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(image),
|
||||
image != nullptr ? static_cast<int>(image->format) : VK_FORMAT_UNDEFINED,
|
||||
static_cast<int>(view_format), swizzle, base_level, level_count, base_layer,
|
||||
layer_count, static_cast<int>(type), image != nullptr ? image->mip_levels : 0,
|
||||
image != nullptr ? image->layers : 0);
|
||||
}
|
||||
return GetImageView(ctx, image,
|
||||
{image->format, type, VK_IMAGE_ASPECT_DEPTH_BIT, base_level, level_count,
|
||||
base_layer, layer_count, swizzle});
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetSampledColorView(GraphicContext* ctx, VulkanImage* image,
|
||||
VkFormat view_format, uint32_t swizzle,
|
||||
uint32_t base_level, uint32_t level_count,
|
||||
VkImageViewType type, uint32_t base_layer,
|
||||
uint32_t layer_count) {
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
view_format == VK_FORMAT_UNDEFINED || base_level >= 16 ||
|
||||
(type != VK_IMAGE_VIEW_TYPE_2D && type != VK_IMAGE_VIEW_TYPE_2D_ARRAY) ||
|
||||
!IsSupportedSampledColorView(image->format, view_format, swizzle)) {
|
||||
EXIT("TextureCache: invalid sampled color view, image=%p swizzle=0x%03x"
|
||||
" view_format=%d mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(image), swizzle, static_cast<int>(view_format), base_level,
|
||||
level_count, base_layer, layer_count, static_cast<int>(type),
|
||||
image != nullptr ? image->mip_levels : 0, image != nullptr ? image->layers : 0);
|
||||
}
|
||||
const auto precreated_view = type == VK_IMAGE_VIEW_TYPE_2D_ARRAY
|
||||
? VulkanImage::VIEW_DEFAULT_ARRAY
|
||||
: VulkanImage::VIEW_DEFAULT;
|
||||
const bool full_view =
|
||||
base_level == 0 && level_count == image->mip_levels && base_layer == 0 &&
|
||||
layer_count == (type == VK_IMAGE_VIEW_TYPE_2D_ARRAY ? image->layers : 1u);
|
||||
if (view_format == image->format && swizzle == DstSel(4, 5, 6, 7) && full_view &&
|
||||
image->image_view[precreated_view] != nullptr) {
|
||||
return image->image_view[precreated_view];
|
||||
}
|
||||
return GetImageView(ctx, image,
|
||||
{view_format, type, VK_IMAGE_ASPECT_COLOR_BIT, base_level, level_count,
|
||||
base_layer, layer_count, swizzle});
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetRenderTargetStorageView(GraphicContext* ctx,
|
||||
RenderTextureVulkanImage* image,
|
||||
VkFormat view_format, uint32_t base_level,
|
||||
uint32_t level_count, VkImageViewType type,
|
||||
uint32_t base_layer, uint32_t layer_count) {
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
view_format == VK_FORMAT_UNDEFINED ||
|
||||
(type != VK_IMAGE_VIEW_TYPE_2D && type != VK_IMAGE_VIEW_TYPE_2D_ARRAY)) {
|
||||
EXIT("TextureCache: invalid render-target storage view, image=%p view_format=%d"
|
||||
" mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(image), static_cast<int>(view_format), base_level,
|
||||
level_count, base_layer, layer_count, static_cast<int>(type),
|
||||
image != nullptr ? image->mip_levels : 0, image != nullptr ? image->layers : 0);
|
||||
}
|
||||
const bool exact = view_format == image->format;
|
||||
const bool compatible = view_format == BgraSrgbStorageViewFormat(image->format);
|
||||
if (!exact && !compatible) {
|
||||
EXIT("TextureCache: incompatible render-target storage view, image_format=%d"
|
||||
" view_format=%d base=%u count=%u\n",
|
||||
static_cast<int>(image->format), static_cast<int>(view_format), base_level,
|
||||
level_count);
|
||||
}
|
||||
if (exact) {
|
||||
const auto index = type == VK_IMAGE_VIEW_TYPE_2D_ARRAY ? VulkanImage::VIEW_STORAGE_ARRAY
|
||||
: VulkanImage::VIEW_STORAGE;
|
||||
const bool full_view =
|
||||
base_level == 0 && level_count == 1 && base_layer == 0 &&
|
||||
layer_count == (type == VK_IMAGE_VIEW_TYPE_2D_ARRAY ? image->layers : 1u);
|
||||
if (full_view && image->image_view[index] != nullptr) {
|
||||
return image->image_view[index];
|
||||
}
|
||||
}
|
||||
|
||||
if (compatible && !ImageViewOps::FormatSupportsStorage(ctx, view_format)) {
|
||||
EXIT("TextureCache: compatible render-target storage format lacks storage support,"
|
||||
" image_format=%d view_format=%d base=%u count=%u\n",
|
||||
static_cast<int>(image->format), static_cast<int>(view_format), base_level,
|
||||
level_count);
|
||||
}
|
||||
return GetImageView(ctx, image,
|
||||
{view_format, type, VK_IMAGE_ASPECT_COLOR_BIT, base_level, level_count,
|
||||
base_layer, layer_count, DstSel(4, 5, 6, 7),
|
||||
VK_IMAGE_USAGE_STORAGE_BIT});
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetStorageTextureSampledView(GraphicContext* ctx,
|
||||
StorageTextureVulkanImage* image,
|
||||
const ImageInfo& info) {
|
||||
const auto shape =
|
||||
SelectStorageSampledViewShape(info.type, info.depth, image != nullptr ? image->layers : 0);
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
shape == StorageSampledViewShape::Unsupported || info.base_array != 0 ||
|
||||
info.levels != image->mip_levels || info.base_level >= info.levels ||
|
||||
info.view_levels == 0 || info.base_level + info.view_levels > info.levels) {
|
||||
EXIT("TextureCache: invalid sampled view of storage texture, image=%p type=%u depth=%u"
|
||||
" base=%u levels=%u view_levels=%u image_levels=%u base_array=%u\n",
|
||||
static_cast<const void*>(image), info.type, info.depth, info.base_level, info.levels,
|
||||
info.view_levels, image != nullptr ? image->mip_levels : 0, info.base_array);
|
||||
}
|
||||
const auto view_format = TextureGetFormat(info.format, TextureFormatUsage::Sampled);
|
||||
if (view_format != image->format && !IsRgba8SrgbReinterpretation(image->format, view_format) &&
|
||||
!IsR32UintFloatReinterpretation(image->format, view_format)) {
|
||||
EXIT("TextureCache: incompatible sampled view of storage texture, image_format=%d"
|
||||
" view_format=%d swizzle=0x%03x\n",
|
||||
static_cast<int>(image->format), static_cast<int>(view_format), info.swizzle);
|
||||
}
|
||||
|
||||
VkImageViewType type = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
|
||||
switch (shape) {
|
||||
case StorageSampledViewShape::Image2D: type = VK_IMAGE_VIEW_TYPE_2D; break;
|
||||
case StorageSampledViewShape::Image2DArray: type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; break;
|
||||
case StorageSampledViewShape::Image3D: type = VK_IMAGE_VIEW_TYPE_3D; break;
|
||||
case StorageSampledViewShape::Unsupported:
|
||||
EXIT("TextureCache: unsupported sampled storage-image view shape\n");
|
||||
}
|
||||
const auto layer_count = shape == StorageSampledViewShape::Image2DArray ? info.depth : 1u;
|
||||
return GetImageView(ctx, image,
|
||||
{view_format, type, VK_IMAGE_ASPECT_COLOR_BIT, info.base_level,
|
||||
info.view_levels, 0, layer_count, info.swizzle});
|
||||
}
|
||||
|
||||
VkImageView TextureCache::GetStorageTextureStorageView(GraphicContext* ctx,
|
||||
StorageTextureVulkanImage* image,
|
||||
uint32_t base_level) {
|
||||
if (ctx == nullptr || image == nullptr || image->image == nullptr ||
|
||||
base_level >= (image != nullptr ? image->mip_levels : 0)) {
|
||||
EXIT("TextureCache: invalid storage-texture mip view, image=%p level=%u levels=%u\n",
|
||||
static_cast<const void*>(image), base_level, image != nullptr ? image->mip_levels : 0);
|
||||
}
|
||||
if (base_level == 0) {
|
||||
return image->image_view[VulkanImage::VIEW_DEFAULT];
|
||||
}
|
||||
return GetImageView(ctx, image,
|
||||
{image->format, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
base_level, 1, 0, 1, DstSel(4, 5, 6, 7),
|
||||
VK_IMAGE_USAGE_STORAGE_BIT});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -214,6 +214,18 @@ ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource
|
||||
}
|
||||
}
|
||||
|
||||
namespace ImageViewOps {
|
||||
|
||||
[[nodiscard]] VkImageAspectFlags DepthAspectMask(VkFormat format) noexcept;
|
||||
[[nodiscard]] bool FormatSupportsStorage(GraphicContext* ctx, VkFormat format);
|
||||
|
||||
void CreateRenderTargetViews(GraphicContext* ctx, RenderTextureVulkanImage* image);
|
||||
void CreateDepthViews(GraphicContext* ctx, DepthStencilVulkanImage* image);
|
||||
void CreateVideoOutViews(GraphicContext* ctx, VideoOutVulkanImage* image);
|
||||
void DestroyViews(GraphicContext* ctx, VulkanImage* image);
|
||||
|
||||
} // namespace ImageViewOps
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
|
||||
@@ -142,6 +142,7 @@ void RenderDispatchDirect(uint64_t submit_id, CommandBuffer* buffer, HW::Context
|
||||
|
||||
void GraphicsRenderInit();
|
||||
void GraphicsRenderCreateContext();
|
||||
void GraphicsRenderReleaseThreadCommandPools();
|
||||
|
||||
[[nodiscard]] bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode,
|
||||
|
||||
@@ -267,7 +267,8 @@ static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, cons
|
||||
continue;
|
||||
}
|
||||
if (resource.written || !resource.read || resource.atomic || descriptor.Base48() == 0 ||
|
||||
descriptor_size == 0 || cache->HasMetaOverlap(descriptor.Base48(), descriptor_size)) {
|
||||
descriptor_size == 0 ||
|
||||
cache->QueryRegion(descriptor.Base48(), descriptor_size).metadata_pages) {
|
||||
EXIT("unsupported HTile clear side-buffer access\n");
|
||||
}
|
||||
g_render_ctx->GetBufferCache()->ValidateGpuAccess(descriptor.Base48(), descriptor_size,
|
||||
@@ -349,9 +350,7 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
|
||||
return false;
|
||||
}
|
||||
auto* cache = g_render_ctx->GetTextureCache();
|
||||
if (!cache->ClearColorImageFromBuffer(command, descriptor.Base48(), size, packed_clear) &&
|
||||
!cache->ClearDepthImageFromBuffer(command, descriptor.Base48(), size, packed_clear) &&
|
||||
!cache->ClearStencilImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
if (!cache->ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
return false;
|
||||
}
|
||||
static std::atomic<uint32_t> logged_clears {0};
|
||||
|
||||
@@ -217,7 +217,8 @@ void GraphicsRenderDepthStencilBarrier(CommandBuffer* buffer, uint64_t vaddr, ui
|
||||
Common::LockGuard lock(g_render_ctx->GetMutex());
|
||||
|
||||
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
|
||||
auto* native = g_render_ctx->GetTextureCache()->FindDepthTargetByRange(vaddr, size);
|
||||
auto* native =
|
||||
g_render_ctx->GetTextureCache()->FindDepthTargetByRange(buffer, vaddr, size);
|
||||
if (native == nullptr) {
|
||||
EXIT("depth-target barrier range has no cached image\n");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@
|
||||
#include "graphics/host_gpu/renderer/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/tiler.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -29,10 +28,21 @@ struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
class BufferCache;
|
||||
class CommandBuffer;
|
||||
class DummyTextureCache;
|
||||
class ResourceMutex;
|
||||
|
||||
class TextureCache {
|
||||
public:
|
||||
struct RegionInfo {
|
||||
bool image_pages = false;
|
||||
bool image_bytes = false;
|
||||
bool gpu_image_bytes = false;
|
||||
bool non_sampled_pages = false;
|
||||
bool metadata_pages = false;
|
||||
bool metadata_bytes = false;
|
||||
bool gpu_metadata_bytes = false;
|
||||
};
|
||||
|
||||
TextureCache(PageManager& page_manager, BufferCache& buffer_cache,
|
||||
ResourceMutex& resource_mutex);
|
||||
~TextureCache();
|
||||
@@ -50,12 +60,8 @@ public:
|
||||
RegisterVideoOutSurfaces(GraphicContext* ctx, const std::vector<VideoOutInfo>& infos);
|
||||
void RefreshVideoOut(VideoOutVulkanImage* image, bool render_target = false);
|
||||
void UnregisterVideoOutSurfaces(const std::vector<VideoOutVulkanImage*>& images);
|
||||
[[nodiscard]] bool ClearColorImageFromBuffer(CommandBuffer* command, uint64_t vaddr,
|
||||
uint64_t size, uint32_t packed_clear);
|
||||
[[nodiscard]] bool ClearDepthImageFromBuffer(CommandBuffer* command, uint64_t vaddr,
|
||||
uint64_t size, uint32_t packed_clear);
|
||||
[[nodiscard]] bool ClearStencilImageFromBuffer(CommandBuffer* command, uint64_t vaddr,
|
||||
uint64_t size, uint32_t packed_clear);
|
||||
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size,
|
||||
uint32_t packed_clear);
|
||||
void MarkGpuWritten(VulkanImage* image);
|
||||
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
|
||||
@@ -94,17 +100,12 @@ public:
|
||||
StorageTextureVulkanImage* image,
|
||||
uint32_t base_level);
|
||||
[[nodiscard]] DepthStencilVulkanImage*
|
||||
FindDepthTargetByRange(uint64_t vaddr, uint64_t size, bool allow_containing_sampled = false);
|
||||
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasRangeOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasGpuModifiedRangeOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasGpuTargetPageOverlap(uint64_t vaddr, uint64_t size);
|
||||
FindDepthTargetByRange(CommandBuffer* command, uint64_t vaddr, uint64_t size,
|
||||
bool allow_containing_sampled = false);
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t vaddr, uint64_t size);
|
||||
void RegisterMeta(uint64_t vaddr, uint64_t size, uint32_t layers = 1);
|
||||
[[nodiscard]] bool IsMeta(uint64_t vaddr);
|
||||
[[nodiscard]] bool IsMetaRange(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasMetaRangeOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasMetaOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsMetaGpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t vaddr, uint32_t slice);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t vaddr);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t vaddr, uint32_t slice, bool is_clear);
|
||||
@@ -112,14 +113,6 @@ public:
|
||||
PageFaultPhase phase) noexcept;
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
|
||||
static void DeleteGpuTexture(GraphicContext* ctx, GpuTextureVulkanImage* image,
|
||||
VulkanMemory* mem);
|
||||
static void DeleteRenderTexture(GraphicContext* ctx, RenderTextureVulkanImage* image,
|
||||
VulkanMemory* mem);
|
||||
static void DeleteDepthStencil(GraphicContext* ctx, DepthStencilVulkanImage* image,
|
||||
VulkanMemory* mem);
|
||||
static void DeleteVideoOut(GraphicContext* ctx, VideoOutVulkanImage* image, VulkanMemory* mem);
|
||||
|
||||
VulkanImage* GetDummySampledTexture(bool uint_format, bool image_3d);
|
||||
VulkanImage* GetDummyStorageTexture(bool uint_format, bool image_3d);
|
||||
|
||||
@@ -133,7 +126,6 @@ private:
|
||||
uint32_t clear_mask = 0;
|
||||
bool gpu_modified = false;
|
||||
};
|
||||
static void DeleteImageViews(GraphicContext* ctx, VulkanImage* image);
|
||||
[[nodiscard]] VkImageView GetImageView(GraphicContext* ctx, VulkanImage* image,
|
||||
const ImageViewInfo& info);
|
||||
[[nodiscard]] bool HasMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
|
||||
@@ -145,6 +137,8 @@ private:
|
||||
void RetireStorageDepthAliasLocked(GraphicContext* ctx, const ImageInfo& requested);
|
||||
void RegisterImageLocked(CachedImage& image);
|
||||
void UnregisterImageLocked(CachedImage& image, bool release_tracking);
|
||||
[[nodiscard]] VulkanImage* PublishImage(CommandBuffer* command,
|
||||
std::shared_ptr<CachedImage> image);
|
||||
[[nodiscard]] std::vector<CachedImage*>
|
||||
FindImagesInRegionLocked(uint64_t vaddr, uint64_t size, bool page_overlap);
|
||||
void RequireRetirementIsolation(const std::vector<CachedImage*>& retire, const char* operation,
|
||||
@@ -156,12 +150,7 @@ private:
|
||||
void SynchronizeDepthImageToBufferLocked(CachedImage& cached, uint64_t write_address,
|
||||
uint64_t write_size);
|
||||
|
||||
Common::Mutex m_dummy_mutex;
|
||||
std::array<VulkanImage*, 4> m_dummy_sampled_textures {};
|
||||
std::array<VulkanImage*, 4> m_dummy_storage_textures {};
|
||||
std::array<VulkanMemory*, 4> m_dummy_sampled_memory {};
|
||||
std::array<VulkanMemory*, 4> m_dummy_storage_memory {};
|
||||
GraphicContext* m_dummy_ctx = nullptr;
|
||||
std::unique_ptr<DummyTextureCache> m_dummy_textures;
|
||||
TrackingSpinLock m_lock;
|
||||
std::mutex m_fault_mutex;
|
||||
MemoryTracker m_memory_tracker;
|
||||
|
||||
@@ -405,6 +405,17 @@ public:
|
||||
std::memcpy(dst_data, m_mapped_data, size);
|
||||
}
|
||||
|
||||
void Release(GraphicContext* ctx) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_buffer.buffer != nullptr) {
|
||||
EXIT_IF(ctx == nullptr || m_mapped_data == nullptr);
|
||||
VulkanUnmapMemory(ctx, &m_buffer.memory);
|
||||
VulkanDeleteBuffer(ctx, &m_buffer);
|
||||
}
|
||||
m_capacity = 0;
|
||||
m_mapped_data = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
template <bool WaitIdle, typename Recorder>
|
||||
void RecordUpload(GraphicContext* ctx, const void* src_data, uint64_t size,
|
||||
@@ -1078,6 +1089,17 @@ void UtilUploadBuffer(GraphicContext* ctx, StagingBufferType type, VulkanBuffer*
|
||||
GetStagingBuffer(type)->UploadToBuffer(ctx, dst_buffer, src_data, size, dst_offset);
|
||||
}
|
||||
|
||||
void UtilReleaseCachedResources(GraphicContext* ctx) {
|
||||
EXIT_IF(ctx == nullptr);
|
||||
g_texture_staging_buffer.Release(ctx);
|
||||
g_vertex_staging_buffer.Release(ctx);
|
||||
g_readback_staging_buffer.Release(ctx);
|
||||
Common::LockGuard lock(g_compressed_image_copy_mutex);
|
||||
if (g_compressed_image_copy_buffer.buffer != nullptr) {
|
||||
VulkanDeleteBuffer(ctx, &g_compressed_image_copy_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void UtilCopyBuffer(VulkanBuffer* src_buffer, VulkanBuffer* dst_buffer, uint64_t size) {
|
||||
EXIT_IF(size == 0 || size > src_buffer->buffer_size || size > dst_buffer->buffer_size);
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ void UtilFillBuffer(GraphicContext* ctx, void* dst_data, uint64_t size,
|
||||
uint64_t src_layout);
|
||||
void UtilUploadBuffer(GraphicContext* ctx, StagingBufferType type, VulkanBuffer* dst_buffer,
|
||||
uint64_t dst_offset, const void* src_data, uint64_t size);
|
||||
void UtilReleaseCachedResources(GraphicContext* ctx);
|
||||
void UtilCopyBuffer(VulkanBuffer* src_buffer, VulkanBuffer* dst_buffer, uint64_t size);
|
||||
void UtilDownloadBuffer(GraphicContext* ctx, VulkanBuffer* src_buffer, uint64_t src_offset,
|
||||
void* dst_data, uint64_t size);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/sync.h"
|
||||
#include "graphics/host_gpu/renderer/renderDraw.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
@@ -1138,6 +1139,14 @@ public:
|
||||
&context, &image, image.format, 0, 1, VK_IMAGE_VIEW_TYPE_2D, 0, 1);
|
||||
const auto storage_again = texture_cache.GetRenderTargetStorageView(
|
||||
&context, &image, image.format, 0, 1, VK_IMAGE_VIEW_TYPE_2D, 0, 1);
|
||||
const auto attachment = texture_cache.GetRenderTargetAttachmentView(
|
||||
&context, &image, image.format, 0, 0, 1);
|
||||
const auto attachment_again = texture_cache.GetRenderTargetAttachmentView(
|
||||
&context, &image, image.format, 0, 0, 1);
|
||||
const auto attachment_mip = texture_cache.GetRenderTargetAttachmentView(
|
||||
&context, &image, image.format, 1, 0, 1);
|
||||
const auto attachment_array = texture_cache.GetRenderTargetAttachmentView(
|
||||
&context, &image, image.format, 0, 0, 2);
|
||||
Require(name, "cache identity",
|
||||
first != VK_NULL_HANDLE && first_again == first &&
|
||||
second != VK_NULL_HANDLE && second != first &&
|
||||
@@ -1148,14 +1157,17 @@ public:
|
||||
reinterpreted_format != VK_NULL_HANDLE &&
|
||||
reinterpreted_format != native_format &&
|
||||
storage != VK_NULL_HANDLE && storage != native_format &&
|
||||
storage_again == storage && image.view_cache.views.size() == 8,
|
||||
"view cache omitted usage, swizzle, format, type, mip, or layer "
|
||||
"identity");
|
||||
storage_again == storage && attachment != VK_NULL_HANDLE &&
|
||||
attachment_again == attachment && attachment != native_format &&
|
||||
attachment != storage && attachment_mip != VK_NULL_HANDLE &&
|
||||
attachment_mip != attachment && attachment_array != VK_NULL_HANDLE &&
|
||||
attachment_array != attachment && image.view_cache.views.size() == 11,
|
||||
"view cache omitted attachment usage, swizzle, format, type, mip, "
|
||||
"or layer identity");
|
||||
|
||||
for (const auto &view : image.view_cache.views) {
|
||||
vkDestroyImageView(m_device, view.view, nullptr);
|
||||
}
|
||||
image.view_cache.views.clear();
|
||||
ImageViewOps::DestroyViews(&context, &image);
|
||||
Require(name, "view teardown", image.view_cache.views.empty(),
|
||||
"dynamic render-target views survived DestroyViews");
|
||||
vkDestroyImage(m_device, image_handle, nullptr);
|
||||
vkFreeMemory(m_device, memory, nullptr);
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
@@ -1232,19 +1244,30 @@ public:
|
||||
const auto array_view = texture_cache.GetDepthTargetSampledView(
|
||||
&context, &image, VK_FORMAT_R32_SFLOAT, replicated, 0, 1,
|
||||
VK_IMAGE_VIEW_TYPE_2D_ARRAY, 0, 2);
|
||||
const auto attachment = texture_cache.GetDepthTargetAttachmentView(
|
||||
&context, &image, 0, 1);
|
||||
const auto attachment_again = texture_cache.GetDepthTargetAttachmentView(
|
||||
&context, &image, 0, 1);
|
||||
const auto attachment_layer = texture_cache.GetDepthTargetAttachmentView(
|
||||
&context, &image, 1, 1);
|
||||
const auto attachment_array = texture_cache.GetDepthTargetAttachmentView(
|
||||
&context, &image, 0, 2);
|
||||
Require(name, "cache identity",
|
||||
first != VK_NULL_HANDLE && first_again == first &&
|
||||
different_swizzle != VK_NULL_HANDLE &&
|
||||
different_swizzle != first &&
|
||||
different_layer != VK_NULL_HANDLE && different_layer != first &&
|
||||
array_view != VK_NULL_HANDLE && array_view != first &&
|
||||
image.view_cache.views.size() == 4,
|
||||
"sampled depth cache omitted swizzle, type, or layer identity");
|
||||
attachment != VK_NULL_HANDLE && attachment_again == attachment &&
|
||||
attachment != first && attachment_layer != VK_NULL_HANDLE &&
|
||||
attachment_layer != attachment && attachment_array != VK_NULL_HANDLE &&
|
||||
attachment_array != attachment && image.view_cache.views.size() == 7,
|
||||
"depth view cache omitted attachment usage, swizzle, type, or layer "
|
||||
"identity");
|
||||
|
||||
for (const auto &view : image.view_cache.views) {
|
||||
vkDestroyImageView(m_device, view.view, nullptr);
|
||||
}
|
||||
image.view_cache.views.clear();
|
||||
ImageViewOps::DestroyViews(&context, &image);
|
||||
Require(name, "view teardown", image.view_cache.views.empty(),
|
||||
"dynamic depth-target views survived DestroyViews");
|
||||
vkDestroyImage(m_device, image_handle, nullptr);
|
||||
vkFreeMemory(m_device, memory, nullptr);
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
@@ -1255,7 +1278,7 @@ public:
|
||||
auto backing = CreateImage2D(name, 8, 8, VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
{}, 1, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
{}, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
GraphicContext context{};
|
||||
context.physical_device = m_physical_device;
|
||||
@@ -1296,14 +1319,123 @@ public:
|
||||
"video-out sampled views did not use identity fast path and lazy "
|
||||
"mappings");
|
||||
|
||||
for (const auto &view : image.view_cache.views) {
|
||||
vkDestroyImageView(m_device, view.view, nullptr);
|
||||
}
|
||||
image.view_cache.views.clear();
|
||||
ImageViewOps::DestroyViews(&context, &image);
|
||||
Require(name, "view teardown",
|
||||
image.view_cache.views.empty() &&
|
||||
std::all_of(std::begin(image.image_view),
|
||||
std::end(image.image_view),
|
||||
[](VkImageView view) { return view == VK_NULL_HANDLE; }),
|
||||
"dynamic or fixed video-out views survived DestroyViews");
|
||||
backing.view = VK_NULL_HANDLE;
|
||||
DestroyImage(&backing);
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
void CheckQueryRegionImageClassification() {
|
||||
constexpr const char *name = "QueryRegionImageClassification";
|
||||
constexpr uintptr_t base = 0x0000000200100000ull;
|
||||
constexpr uint64_t allocation_size = 0x4000;
|
||||
auto *memory = static_cast<uint8_t *>(VirtualAlloc(
|
||||
reinterpret_cast<void *>(base), allocation_size,
|
||||
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
Require(name, "allocation", memory == reinterpret_cast<void *>(base),
|
||||
"fixed VirtualAlloc failed");
|
||||
std::memset(memory, 0, allocation_size);
|
||||
|
||||
EnsureRuntimeContext();
|
||||
ResourceMutex resource_mutex;
|
||||
PageManager page_manager(RejectUnexpectedPageFault, nullptr);
|
||||
BufferCache buffer_cache(page_manager, resource_mutex);
|
||||
TextureCache texture_cache(page_manager, buffer_cache, resource_mutex);
|
||||
buffer_cache.SetTextureCache(texture_cache);
|
||||
page_manager.OnGpuMap(base, allocation_size);
|
||||
|
||||
constexpr auto format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm);
|
||||
constexpr auto linear =
|
||||
Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
|
||||
ImageInfo sampled_info{};
|
||||
sampled_info.address = base + 0x100;
|
||||
sampled_info.format = format;
|
||||
sampled_info.width = 4;
|
||||
sampled_info.height = 4;
|
||||
sampled_info.pitch = TileGetTexturePitch(format, sampled_info.width, 1, linear);
|
||||
sampled_info.tile = linear;
|
||||
sampled_info.type = Prospero::GpuEnumValue(Prospero::ImageType::kColor2D);
|
||||
TileSizeAlign sampled_size{};
|
||||
TileGetTextureTotalSize(format, sampled_info.width, sampled_info.height,
|
||||
sampled_info.depth, sampled_info.pitch,
|
||||
sampled_info.levels, linear, false, &sampled_size);
|
||||
sampled_info.size = sampled_size.size;
|
||||
Require(name, "sampled fixture",
|
||||
sampled_info.size != 0 && sampled_size.align != 0 &&
|
||||
sampled_info.size <= 0x600 &&
|
||||
sampled_info.address % sampled_size.align == 0,
|
||||
"sampled image does not fit the intended sub-page fixture");
|
||||
|
||||
RenderTargetInfo target_info{};
|
||||
target_info.address = base + 0x1100;
|
||||
target_info.size = 0x100;
|
||||
target_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
target_info.width = 4;
|
||||
target_info.height = 4;
|
||||
target_info.pitch = 4;
|
||||
target_info.bytes_per_element = 4;
|
||||
target_info.tile_mode = linear;
|
||||
|
||||
{
|
||||
CommandBuffer command(GraphicContext::QUEUE_GFX);
|
||||
auto *sampled =
|
||||
texture_cache.FindTexture(&command, &m_runtime_context, sampled_info, false);
|
||||
auto *target =
|
||||
texture_cache.FindRenderTarget(&command, &m_runtime_context, target_info);
|
||||
Require(name, "registration", sampled != nullptr && target != nullptr,
|
||||
"sampled or non-sampled image registration failed");
|
||||
|
||||
const auto sampled_bytes =
|
||||
texture_cache.QueryRegion(sampled_info.address + 0x20, 0x20);
|
||||
const auto sampled_page = texture_cache.QueryRegion(base + 0x800, 0x20);
|
||||
Require(name, "sampled classification",
|
||||
sampled_bytes.image_pages && sampled_bytes.image_bytes &&
|
||||
!sampled_bytes.gpu_image_bytes &&
|
||||
!sampled_bytes.non_sampled_pages && sampled_page.image_pages &&
|
||||
!sampled_page.image_bytes && !sampled_page.gpu_image_bytes &&
|
||||
!sampled_page.non_sampled_pages,
|
||||
"sampled image page and byte ownership were not separated");
|
||||
|
||||
const auto target_bytes =
|
||||
texture_cache.QueryRegion(target_info.address + 0x20, 0x20);
|
||||
const auto target_page =
|
||||
texture_cache.QueryRegion(target_info.address + 0x400, 0x20);
|
||||
Require(name, "non-sampled classification",
|
||||
target_bytes.image_pages && target_bytes.image_bytes &&
|
||||
!target_bytes.gpu_image_bytes && target_bytes.non_sampled_pages &&
|
||||
target_page.image_pages && !target_page.image_bytes &&
|
||||
!target_page.gpu_image_bytes && target_page.non_sampled_pages,
|
||||
"non-sampled image page and byte ownership were not separated");
|
||||
|
||||
texture_cache.MarkGpuWritten(target);
|
||||
const auto gpu_bytes =
|
||||
texture_cache.QueryRegion(target_info.address + 0x20, 0x20);
|
||||
const auto gpu_page =
|
||||
texture_cache.QueryRegion(target_info.address + 0x400, 0x20);
|
||||
Require(name, "GPU ownership",
|
||||
gpu_bytes.image_pages && gpu_bytes.image_bytes &&
|
||||
gpu_bytes.gpu_image_bytes && gpu_bytes.non_sampled_pages &&
|
||||
gpu_page.image_pages && !gpu_page.image_bytes &&
|
||||
!gpu_page.gpu_image_bytes && gpu_page.non_sampled_pages,
|
||||
"GPU ownership escaped the target's exact byte range");
|
||||
|
||||
texture_cache.UnmapMemory(base, allocation_size);
|
||||
page_manager.OnGpuUnmap(base, allocation_size);
|
||||
}
|
||||
Require(name, "free", VirtualFree(memory, 0, MEM_RELEASE) != 0,
|
||||
"VirtualFree failed");
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
#endif
|
||||
|
||||
Buffer CreateStorageBuffer(const char *shader_name,
|
||||
const std::vector<u32> &initial,
|
||||
size_t dword_count) {
|
||||
@@ -2238,6 +2370,37 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void EnsureRuntimeContext() {
|
||||
if (m_runtime_context.allocator != VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
m_runtime_context.instance = m_instance;
|
||||
m_runtime_context.physical_device = m_physical_device;
|
||||
m_runtime_context.device = m_device;
|
||||
vkGetPhysicalDeviceProperties(m_physical_device,
|
||||
&m_runtime_context.physical_device_properties);
|
||||
m_runtime_context.physical_device_memory_properties = m_memory_properties;
|
||||
for (auto &queue : m_runtime_context.queues) {
|
||||
queue.mutex = &m_runtime_queue_mutex;
|
||||
queue.family = m_queue_family;
|
||||
queue.index = 0;
|
||||
queue.vk_queue = m_queue;
|
||||
}
|
||||
|
||||
VmaAllocatorCreateInfo allocator_info{};
|
||||
allocator_info.instance = m_instance;
|
||||
allocator_info.physicalDevice = m_physical_device;
|
||||
allocator_info.device = m_device;
|
||||
allocator_info.vulkanApiVersion = VK_API_VERSION_1_2;
|
||||
RequireVk("VulkanHarness", "runtime context",
|
||||
vmaCreateAllocator(&allocator_info, &m_runtime_context.allocator),
|
||||
"vmaCreateAllocator");
|
||||
if (g_render_ctx == nullptr) {
|
||||
GraphicsRenderInit();
|
||||
}
|
||||
g_render_ctx->SetGraphicCtx(&m_runtime_context);
|
||||
}
|
||||
|
||||
void Init() {
|
||||
VkApplicationInfo app{};
|
||||
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
@@ -2330,6 +2493,12 @@ private:
|
||||
void Destroy() {
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(m_device);
|
||||
if (m_runtime_context.allocator != VK_NULL_HANDLE) {
|
||||
GraphicsRenderReleaseThreadCommandPools();
|
||||
UtilReleaseCachedResources(&m_runtime_context);
|
||||
vmaDestroyAllocator(m_runtime_context.allocator);
|
||||
m_runtime_context.allocator = VK_NULL_HANDLE;
|
||||
}
|
||||
if (m_command_pool != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(m_device, m_command_pool, nullptr);
|
||||
}
|
||||
@@ -2565,6 +2734,8 @@ private:
|
||||
VkCommandPool m_command_pool = VK_NULL_HANDLE;
|
||||
u32 m_queue_family = 0;
|
||||
VkPhysicalDeviceMemoryProperties m_memory_properties{};
|
||||
Common::Mutex m_runtime_queue_mutex;
|
||||
GraphicContext m_runtime_context{};
|
||||
};
|
||||
|
||||
void CompareWords(const TestCase &test, const char *stage,
|
||||
@@ -11135,7 +11306,7 @@ void CheckOverlappingMetadataViews() {
|
||||
page_manager.HandleFault(PageFaultAccess::Write, second),
|
||||
"shared metadata page did not transfer to CPU ownership");
|
||||
Require("OverlappingMetadataViews", "ownership",
|
||||
!texture_cache.IsMetaGpuModified(second, 0x1000),
|
||||
!texture_cache.QueryRegion(second, 0x1000).gpu_metadata_bytes,
|
||||
"shared metadata page retained GPU ownership after a CPU fault");
|
||||
|
||||
texture_cache.UnmapMemory(base, allocation_size);
|
||||
@@ -11145,6 +11316,62 @@ void CheckOverlappingMetadataViews() {
|
||||
std::printf("[host] %-32s ok\n", "OverlappingMetadataViews");
|
||||
}
|
||||
|
||||
void CheckQueryRegionAggregation() {
|
||||
constexpr uintptr_t base = 0x0000000200010000ull;
|
||||
constexpr uint64_t allocation_size = 0x10000;
|
||||
constexpr uint64_t metadata_size = 0x180;
|
||||
auto *memory = static_cast<uint8_t *>(VirtualAlloc(
|
||||
reinterpret_cast<void *>(base), allocation_size,
|
||||
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
Require("QueryRegionAggregation", "allocation",
|
||||
memory == reinterpret_cast<void *>(base), "fixed VirtualAlloc failed");
|
||||
|
||||
ResourceMutex resource_mutex;
|
||||
CacheFaultContext fault_context;
|
||||
PageManager page_manager(CacheFault, &fault_context);
|
||||
BufferCache buffer_cache(page_manager, resource_mutex);
|
||||
TextureCache texture_cache(page_manager, buffer_cache, resource_mutex);
|
||||
fault_context.texture = &texture_cache;
|
||||
buffer_cache.SetTextureCache(texture_cache);
|
||||
page_manager.OnGpuMap(base, allocation_size);
|
||||
|
||||
const auto empty = texture_cache.QueryRegion(base + 0x20, 0x40);
|
||||
Require("QueryRegionAggregation", "empty",
|
||||
!empty.image_pages && !empty.image_bytes && !empty.gpu_image_bytes &&
|
||||
!empty.non_sampled_pages && !empty.metadata_pages &&
|
||||
!empty.metadata_bytes && !empty.gpu_metadata_bytes,
|
||||
"empty region reported cached ownership");
|
||||
|
||||
texture_cache.RegisterMeta(base, metadata_size);
|
||||
const auto bytes = texture_cache.QueryRegion(base + 0x20, 0x40);
|
||||
const auto page_only = texture_cache.QueryRegion(base + 0x400, 0x40);
|
||||
const auto disjoint = texture_cache.QueryRegion(base + 0x1000, 0x40);
|
||||
Require("QueryRegionAggregation", "page and byte overlap",
|
||||
bytes.metadata_pages && bytes.metadata_bytes &&
|
||||
!bytes.gpu_metadata_bytes && page_only.metadata_pages &&
|
||||
!page_only.metadata_bytes && !page_only.gpu_metadata_bytes &&
|
||||
!disjoint.metadata_pages && !disjoint.metadata_bytes &&
|
||||
!bytes.image_pages && !bytes.image_bytes &&
|
||||
!bytes.gpu_image_bytes && !bytes.non_sampled_pages,
|
||||
"metadata page candidates were not separated from exact byte overlap");
|
||||
|
||||
Require("QueryRegionAggregation", "clear", texture_cache.ClearMeta(base),
|
||||
"metadata clear setup failed");
|
||||
const auto gpu_bytes = texture_cache.QueryRegion(base + 0x20, 0x40);
|
||||
const auto gpu_page_only = texture_cache.QueryRegion(base + 0x400, 0x40);
|
||||
Require("QueryRegionAggregation", "GPU ownership",
|
||||
gpu_bytes.metadata_pages && gpu_bytes.metadata_bytes &&
|
||||
gpu_bytes.gpu_metadata_bytes && gpu_page_only.metadata_pages &&
|
||||
!gpu_page_only.metadata_bytes && !gpu_page_only.gpu_metadata_bytes,
|
||||
"GPU metadata ownership escaped its exact registered byte range");
|
||||
|
||||
texture_cache.UnmapMemory(base, allocation_size);
|
||||
page_manager.OnGpuUnmap(base, allocation_size);
|
||||
Require("QueryRegionAggregation", "free", VirtualFree(memory, 0, MEM_RELEASE) != 0,
|
||||
"VirtualFree failed");
|
||||
std::printf("[host] %-32s ok\n", "QueryRegionAggregation");
|
||||
}
|
||||
|
||||
void CheckGpuMetadataReuse() {
|
||||
constexpr uintptr_t base = 0x0000000200010000ull;
|
||||
constexpr uint64_t allocation_size = 0x20000;
|
||||
@@ -11173,7 +11400,7 @@ void CheckGpuMetadataReuse() {
|
||||
texture_cache.InvalidateMemoryFromGPU(base, allocation_size);
|
||||
Require("GpuMetadataReuse", "discard",
|
||||
!full_image_transition && !texture_cache.IsMeta(base) &&
|
||||
!texture_cache.HasMetaRangeOverlap(base, allocation_size),
|
||||
!texture_cache.QueryRegion(base, allocation_size).metadata_bytes,
|
||||
"metadata-only overwrite retained identity or claimed an image transition");
|
||||
texture_cache.RegisterMeta(base, metadata_size, layers);
|
||||
Require("GpuMetadataReuse", "re-register",
|
||||
@@ -12721,8 +12948,8 @@ void CheckHostDmaMetadataReuse() {
|
||||
reinterpret_cast<uint32_t *>(memory + 0x1ffc)[0] == 0x11223344,
|
||||
"post-clear CPU DMA fill did not publish backing");
|
||||
Require("HostDmaMetadataReuse", "identity",
|
||||
texture_cache.HasMetaOverlap(base, metadata_size) &&
|
||||
!texture_cache.IsMetaGpuModified(base, 0x2000) &&
|
||||
texture_cache.QueryRegion(base, metadata_size).metadata_pages &&
|
||||
!texture_cache.QueryRegion(base, 0x2000).gpu_metadata_bytes &&
|
||||
VirtualQuery(memory, &protection, sizeof(protection)) != 0 &&
|
||||
protection.Protect == PAGE_READWRITE,
|
||||
"CPU writes erased metadata identity or retained virtual ownership");
|
||||
@@ -12827,6 +13054,9 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) {
|
||||
CheckImageOverlapResolution();
|
||||
CheckQueryRegionAggregation();
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckQueryRegionImageClassification();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--htile-clear-only") == 0) {
|
||||
@@ -12935,6 +13165,7 @@ int main(int argc, char **argv) {
|
||||
CheckGpuMetadataReuse();
|
||||
CheckMetadataReuseDescriptors();
|
||||
CheckImageOverlapResolution();
|
||||
CheckQueryRegionAggregation();
|
||||
CheckMsaaCompatibility();
|
||||
CheckDepthHtileStencilCompatibility();
|
||||
CheckStencilAttachmentAccess();
|
||||
@@ -12951,6 +13182,7 @@ int main(int argc, char **argv) {
|
||||
CheckEmbeddedFetchLaneSpill();
|
||||
CheckPs5GameExampleImageClearRuntimeShape();
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckQueryRegionImageClassification();
|
||||
vulkan.CheckMutableStorageSrgbView();
|
||||
vulkan.CheckMutableRenderTargetBgraStorageView();
|
||||
vulkan.CheckRenderTargetViewCache();
|
||||
|
||||
Reference in New Issue
Block a user