Files
KytyPS5/src/graphics/host_gpu/renderer/imageInfo.h
T
2026-07-21 07:37:52 +02:00

1219 lines
57 KiB
C++

#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
namespace Libs::Graphics {
struct ImageInfo {
uint64_t address = 0;
uint64_t size = 0;
uint32_t format = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t base_level = 0;
uint32_t levels = 1;
uint32_t view_levels = 1;
uint32_t tile = 0;
uint32_t swizzle = 0;
uint32_t depth = 1;
uint32_t type = 0;
uint32_t base_array = 0;
};
struct RenderTargetInfo {
uint64_t address = 0;
uint64_t size = 0;
vk::Format format = vk::Format::eUndefined;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t levels = 1;
uint32_t layers = 1;
uint32_t samples = 1;
};
// Common image-to-buffer copy description. Storage images and render targets keep distinct
// cache records today, but downloads should consume one normalized layout just as uploads do.
struct ColorImageTransferInfo {
uint64_t address = 0;
uint64_t size = 0;
vk::Format format = vk::Format::eUndefined;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t levels = 1;
uint32_t samples = 1;
};
[[nodiscard]] inline ColorImageTransferInfo
MakeColorImageTransferInfo(const ImageInfo& info, vk::Format format,
uint32_t bytes_per_element) noexcept {
return {info.address, info.size, format, info.width, info.height,
info.pitch, bytes_per_element, info.tile, info.levels, 1};
}
[[nodiscard]] inline ColorImageTransferInfo
MakeColorImageTransferInfo(const RenderTargetInfo& info) noexcept {
return {
info.address, info.size, info.format, info.width, info.height, info.pitch,
info.bytes_per_element, info.tile_mode, info.levels, info.samples};
}
struct DepthTargetInfo {
uint64_t address = 0;
uint64_t size = 0;
uint64_t stencil_address = 0;
uint64_t stencil_size = 0;
uint64_t htile_address = 0;
uint64_t htile_size = 0;
vk::Format format = vk::Format::eUndefined;
uint32_t guest_format = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t layers = 1;
uint32_t samples = 1;
bool depth_load_clear = false;
bool depth_access = false;
bool stencil_load_clear = false;
bool stencil_access = false;
bool stencil_htile_compressed = false;
};
struct DepthFormatPolicy {
Prospero::DepthFormat depth_format;
Prospero::BufferFormat guest_format;
uint32_t bytes_per_element;
vk::Format sampled_view_format;
vk::Format depth_attachment_format;
std::array<vk::Format, 3> stencil_attachment_formats;
};
inline constexpr std::array<DepthFormatPolicy, 2> DEPTH_FORMAT_POLICIES {{
{Prospero::DepthFormat::kZ16,
Prospero::BufferFormat::k16UNorm,
2,
vk::Format::eR16Unorm,
vk::Format::eD16Unorm,
{vk::Format::eD16UnormS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}},
{Prospero::DepthFormat::kZ32F,
Prospero::BufferFormat::k32Float,
4,
vk::Format::eR32Sfloat,
vk::Format::eD32Sfloat,
{vk::Format::eD32SfloatS8Uint, vk::Format::eUndefined, vk::Format::eUndefined}},
}};
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindDepthFormatPolicy(uint32_t depth_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.depth_format) == depth_format) {
return &policy;
}
}
return nullptr;
}
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.guest_format) == guest_format) {
return &policy;
}
}
return nullptr;
}
[[nodiscard]] inline constexpr bool IsStencilAttachmentFormat(const DepthFormatPolicy& policy,
vk::Format format) noexcept {
for (const auto candidate: policy.stencil_attachment_formats) {
if (candidate != vk::Format::eUndefined && candidate == format) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(const DepthFormatPolicy& policy,
bool has_stencil) noexcept {
return has_stencil ? policy.stencil_attachment_formats.front() : policy.depth_attachment_format;
}
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(uint32_t depth_format,
uint32_t stencil_format) noexcept {
bool has_stencil = false;
switch (static_cast<Prospero::StencilFormat>(stencil_format)) {
case Prospero::StencilFormat::kInvalid: break;
case Prospero::StencilFormat::k8UInt: has_stencil = true; break;
default: return vk::Format::eUndefined;
}
const auto* policy = FindDepthFormatPolicy(depth_format);
return policy == nullptr ? vk::Format::eUndefined : DepthAttachmentFormat(*policy, has_stencil);
}
[[nodiscard]] inline constexpr vk::ImageUsageFlags DepthTargetImageUsage() noexcept {
return vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled |
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
}
[[nodiscard]] inline constexpr vk::Format DepthAspectTransferFormat(vk::Format format) noexcept {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eD16UnormS8Uint: return vk::Format::eD16Unorm;
case vk::Format::eD24UnormS8Uint: return vk::Format::eX8D24UnormPack32;
case vk::Format::eD32Sfloat:
case vk::Format::eD32SfloatS8Uint: return vk::Format::eD32Sfloat;
default: return vk::Format::eUndefined;
}
}
[[nodiscard]] inline constexpr uint32_t DepthAspectTransferBytes(vk::Format format) noexcept {
switch (DepthAspectTransferFormat(format)) {
case vk::Format::eD16Unorm: return 2;
case vk::Format::eX8D24UnormPack32:
case vk::Format::eD32Sfloat: return 4;
default: return 0;
}
}
[[nodiscard]] inline constexpr uint32_t EncodeD16AsD24(uint16_t value) noexcept {
// Preserve the guest UNORM value when widening 16 bits to the 24-bit transfer plane.
return static_cast<uint32_t>((static_cast<uint64_t>(value) * 0x00ffffffu + 0x7fffu) / 0xffffu);
}
[[nodiscard]] inline uint32_t EncodeD16AsD32(uint16_t value) noexcept {
return std::bit_cast<uint32_t>(static_cast<float>(value) / 65535.0f);
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
uint32_t guest_format,
vk::Format view_format) noexcept {
const auto* policy = FindGuestDepthFormatPolicy(guest_format);
return policy != nullptr && view_format == policy->sampled_view_format &&
(image_format == policy->depth_attachment_format ||
IsStencilAttachmentFormat(*policy, image_format));
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
vk::Format view_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (IsSupportedSampledDepthFormat(image_format, Prospero::GpuEnumValue(policy.guest_format),
view_format)) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr bool IsSupportedDepthTargetFormat(const DepthTargetInfo& info) {
const bool has_stencil = info.stencil_address != 0 || info.stencil_size != 0;
const auto* policy = FindGuestDepthFormatPolicy(info.guest_format);
return policy != nullptr && info.bytes_per_element == policy->bytes_per_element &&
(has_stencil ? IsStencilAttachmentFormat(*policy, info.format)
: info.format == policy->depth_attachment_format);
}
[[nodiscard]] inline constexpr bool IsSupportedDepthReadbackFormat(const DepthTargetInfo& info) {
const bool has_stencil = info.stencil_address != 0 || info.stencil_size != 0;
return IsSupportedDepthTargetFormat(info) &&
DepthAspectTransferBytes(info.format) == info.bytes_per_element &&
(!has_stencil || !info.stencil_htile_compressed);
}
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
struct VideoOutInfo {
uint64_t address = 0;
uint64_t size = 0;
uint64_t metadata_address = 0;
vk::Format format = vk::Format::eUndefined;
uint32_t guest_format = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t dcc_control = 0;
VideoOutCompression compression = VideoOutCompression::Unsupported;
bool bgra16 = false;
};
[[nodiscard]] inline VideoOutCompression
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
uint64_t dcc_clear_color) noexcept {
constexpr uint32_t VIDEO_OUT_DCC_CONTROL_256_256_0 = 0x00000048u;
constexpr uint32_t VIDEO_OUT_DCC_CONTROL_256_64_64 = 0x00000208u;
if (!compressed) {
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
? VideoOutCompression::Uncompressed
: VideoOutCompression::Unsupported;
}
if (metadata_address == 0 || (metadata_address & 0xffu) != 0 || dcc_clear_color != 0) {
return VideoOutCompression::Unsupported;
}
switch (dcc_control) {
case VIDEO_OUT_DCC_CONTROL_256_256_0: return VideoOutCompression::Dcc256_256_0;
case VIDEO_OUT_DCC_CONTROL_256_64_64: return VideoOutCompression::Dcc256_64_64;
default: return VideoOutCompression::Unsupported;
}
}
[[nodiscard]] inline constexpr bool
CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_target,
bool gpu_modified, bool guest_modified) noexcept {
return compression != VideoOutCompression::Uncompressed &&
compression != VideoOutCompression::Unsupported && !guest_modified &&
(render_target || gpu_modified);
}
struct VideoOutPixelFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t guest_format = 0;
uint32_t bytes_per_element = 0;
bool bgra16 = false;
};
struct VideoOutFormatPolicy {
uint64_t pixel_format;
VideoOutPixelFormatInfo info;
};
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{0x8000000000000000ull,
{vk::Format::eB8G8R8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{0x8100000022000000ull,
{vk::Format::eA2B10G10R10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{0x8100000000000000ull,
{vk::Format::eA2R10G10B10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{0xc001000622000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, false}},
{0xc001000600000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, true}},
}};
[[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
VideoOutPixelFormatInfo& info) {
for (const auto& policy: VIDEO_OUT_FORMAT_POLICIES) {
if (policy.pixel_format == pixel_format) {
info = policy.info;
return true;
}
}
return false;
}
[[nodiscard]] inline bool IsSupportedVideoOutFormat(const VideoOutInfo& info) {
for (const auto& policy: VIDEO_OUT_FORMAT_POLICIES) {
if (info.format == policy.info.format && info.guest_format == policy.info.guest_format &&
info.bytes_per_element == policy.info.bytes_per_element &&
info.bgra16 == policy.info.bgra16) {
return true;
}
}
return false;
}
enum class DepthOverlap : uint8_t {
None,
RetireSampled,
RetireStorage,
ExpandTarget,
DiscardTarget,
RecreateTarget,
Unsupported
};
enum class DepthTransitionSource : uint8_t { None, Guest, Native };
enum class RenderTargetOverlap : uint8_t {
None,
RetireSampled,
RetireStorage,
PreserveStorage,
ExpandTarget,
RetireTarget,
Unsupported
};
enum class SampledOverlap : uint8_t { None, ReadOnlyAlias, Unsupported };
enum class StorageSampledOverlap : uint8_t { None, ExactImage, RetireStorage, Unsupported };
enum class StorageSampledViewShape : uint8_t { Image2D, Image2DArray, Image3D, Unsupported };
enum class StorageImageOverlap : uint8_t { None, RetireSampled, PageNeighbor, Unsupported };
enum class HostWriteOverlap : uint8_t { None, InvalidateImage, Unsupported };
enum class BufferImageBinding : uint8_t {
Texture,
VideoOut,
RenderTarget,
StorageTexture,
DepthTarget,
Unsupported
};
enum class BufferImageWrite : uint8_t {
None,
InvalidateTexture,
InvalidateVideoOut,
InvalidateStorageTexture,
InvalidateDepthTarget,
InvalidateRenderTarget,
SynchronizeRenderTarget,
SynchronizeStorageTexture,
SynchronizeDepthTarget,
SynchronizeVideoOut,
Unsupported
};
[[nodiscard]] inline constexpr bool
HasGuestCurrentImageOwnership(bool image_gpu_modified, bool buffer_modified, bool cpu_dirty,
bool tracker_gpu_modified) noexcept {
return !image_gpu_modified && !buffer_modified && !cpu_dirty && !tracker_gpu_modified;
}
enum class StorageBufferRebind : uint8_t { Reuse, RefreshFromBacking, Unsupported };
enum class MetaImageOverlap : uint8_t { RetainSampled, RetireImage, Unsupported };
[[nodiscard]] inline constexpr bool CanRetireGuestCurrentDepthForMetadataReuse(
bool depth_gpu_modified, bool depth_buffer_modified, bool depth_tracker_gpu_modified,
bool metadata_gpu_modified, bool metadata_tracker_gpu_modified,
uint32_t metadata_clear_mask) noexcept {
return !metadata_gpu_modified && !metadata_tracker_gpu_modified && metadata_clear_mask == 0 &&
HasGuestCurrentImageOwnership(depth_gpu_modified, depth_buffer_modified, false,
depth_tracker_gpu_modified);
}
[[nodiscard]] inline constexpr uint32_t SelectImageBackingBaseLevel(bool storage,
uint32_t view_base_level) {
// Storage descriptors select a per-mip view of one full allocation. Backing creation must not
// depend on which view happens to be bound first.
return storage ? 0u : view_base_level;
}
[[nodiscard]] inline constexpr bool
IsDepthUintTextureReinterpretation(vk::Format image_format, uint32_t guest_format,
vk::Format view_format) noexcept {
switch (image_format) {
case vk::Format::eD32Sfloat:
return guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) &&
view_format == vk::Format::eR32Uint;
default: return false;
}
}
[[nodiscard]] inline constexpr bool NeedsStaticSampledArrayView(bool shader_array,
bool dynamic_view_selected) {
return shader_array && !dynamic_view_selected;
}
[[nodiscard]] inline constexpr StorageSampledViewShape
SelectStorageSampledViewShape(uint32_t type, uint32_t depth, uint32_t backing_layers) noexcept {
switch (static_cast<Prospero::ImageType>(type)) {
case Prospero::ImageType::kColor2D:
return depth == 1 && backing_layers == 1 ? StorageSampledViewShape::Image2D
: StorageSampledViewShape::Unsupported;
case Prospero::ImageType::kColor2DArray:
return depth != 0 && depth == backing_layers ? StorageSampledViewShape::Image2DArray
: StorageSampledViewShape::Unsupported;
case Prospero::ImageType::kColor3D:
return depth != 0 && backing_layers == 1 ? StorageSampledViewShape::Image3D
: StorageSampledViewShape::Unsupported;
default: return StorageSampledViewShape::Unsupported;
}
}
[[nodiscard]] inline constexpr bool IsSupportedRenderTargetElementSize(uint32_t size) noexcept {
switch (size) {
case 1:
case 2:
case 4:
case 8: return true;
default: return false;
}
}
[[nodiscard]] inline constexpr bool
IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
}
[[nodiscard]] inline constexpr bool
IsSupportedStandard64RenderTarget(const RenderTargetInfo& info) noexcept {
if (info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) ||
info.address == 0 || (info.address & 0xffffu) != 0 || info.width == 0 || info.height == 0 ||
info.bytes_per_element != 4 || info.levels != 1 || info.layers != 1 || info.samples != 1) {
return false;
}
const auto expected_pitch = (static_cast<uint64_t>(info.width) + 127u) & ~uint64_t {127u};
const auto padded_height = (static_cast<uint64_t>(info.height) + 127u) & ~uint64_t {127u};
return expected_pitch <= UINT32_MAX && info.pitch == expected_pitch &&
expected_pitch <= UINT64_MAX / padded_height / info.bytes_per_element &&
info.size == expected_pitch * padded_height * info.bytes_per_element;
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const RenderTargetInfo& info) noexcept {
return info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
IsSupportedStandard64RenderTarget(info);
}
[[nodiscard]] inline constexpr DepthTransitionSource
SelectDepthTransitionSource(bool depth_load_clear, bool sampled_native_available,
bool sampled_cpu_dirty, bool sampled_buffer_modified,
bool buffer_overlap, bool buffer_cpu_dirty) noexcept {
if (depth_load_clear) {
return DepthTransitionSource::None;
}
return sampled_native_available && !sampled_cpu_dirty && !sampled_buffer_modified &&
!(buffer_overlap && buffer_cpu_dirty)
? DepthTransitionSource::Native
: DepthTransitionSource::Guest;
}
[[nodiscard]] inline MetaImageOverlap ClassifyMetaImageOverlap(bool sampled, bool writable_image,
bool gpu_modified,
bool buffer_modified,
bool cpu_dirty) {
if (sampled && !gpu_modified) {
return MetaImageOverlap::RetainSampled;
}
if (writable_image && !gpu_modified && !buffer_modified && !cpu_dirty) {
return MetaImageOverlap::RetireImage;
}
return MetaImageOverlap::Unsupported;
}
[[nodiscard]] inline float DecodeSrgbClearComponent(uint32_t value) {
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
}
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
switch (format) {
case vk::Format::eR8G8B8A8Srgb:
next.float32[0] = DecodeSrgbClearComponent(packed);
next.float32[1] = DecodeSrgbClearComponent(packed >> 8u);
next.float32[2] = DecodeSrgbClearComponent(packed >> 16u);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eB8G8R8A8Srgb:
next.float32[0] = DecodeSrgbClearComponent(packed >> 16u);
next.float32[1] = DecodeSrgbClearComponent(packed >> 8u);
next.float32[2] = DecodeSrgbClearComponent(packed);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eR8G8B8A8Unorm:
next.float32[0] = unorm8(packed);
next.float32[1] = unorm8(packed >> 8u);
next.float32[2] = unorm8(packed >> 16u);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eB8G8R8A8Unorm:
next.float32[0] = unorm8(packed >> 16u);
next.float32[1] = unorm8(packed >> 8u);
next.float32[2] = unorm8(packed);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eA2B10G10R10UnormPack32:
next.float32[0] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
next.float32[2] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
break;
case vk::Format::eA2R10G10B10UnormPack32:
next.float32[0] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
next.float32[2] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
break;
default: return false;
}
clear = next;
return true;
}
[[nodiscard]] inline bool DecodePackedStencilClear(uint32_t packed, uint8_t& clear) {
const auto value = static_cast<uint8_t>(packed);
if (packed != static_cast<uint32_t>(value) * 0x01010101u) {
return false;
}
clear = value;
return true;
}
[[nodiscard]] inline bool DecodePackedDepthClear(vk::Format format, uint32_t packed, float& clear) {
if (format != vk::Format::eD32Sfloat && format != vk::Format::eD32SfloatS8Uint) {
return false;
}
const auto value = std::bit_cast<float>(packed);
if (!std::isfinite(value) || value < 0.0f || value > 1.0f) {
return false;
}
clear = value;
return true;
}
[[nodiscard]] inline bool CanNativeClearDepthFromBuffer(const DepthTargetInfo& target,
uint64_t address, uint64_t size) {
const bool d32 =
target.format == vk::Format::eD32Sfloat || target.format == vk::Format::eD32SfloatS8Uint;
return address == target.address && size == target.size && target.layers == 1 && d32 &&
target.samples == 1 &&
target.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
target.bytes_per_element == 4 &&
target.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
target.htile_address == 0 && target.htile_size == 0;
}
[[nodiscard]] inline bool CanLoadStencilAttachment(const DepthTargetInfo& target,
bool stencil_initialized) {
const bool has_stencil = target.stencil_address != 0 || target.stencil_size != 0;
return !has_stencil || !target.stencil_access || target.stencil_load_clear ||
stencil_initialized;
}
[[nodiscard]] inline bool CanLoadRawStencilPlane(const DepthTargetInfo& target) {
const bool has_stencil = target.stencil_address != 0 || target.stencil_size != 0;
return has_stencil && !target.stencil_htile_compressed;
}
[[nodiscard]] inline bool IsDepthTargetRangeCompatible(const DepthTargetInfo& target,
uint64_t address, uint64_t size) {
if (address == 0 || size == 0 || address > UINT64_MAX - size) {
EXIT("invalid depth-target view range\n");
}
const bool depth = address == target.address && size <= target.size;
const bool stencil = target.stencil_address != 0 && address == target.stencil_address &&
size == target.stencil_size;
return depth || stencil;
}
[[nodiscard]] inline bool ImageRangeOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) {
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
right > UINT64_MAX - right_size) {
EXIT("invalid image overlap range\n");
}
return left < right + right_size && right < left + left_size;
}
[[nodiscard]] inline bool ImagePageRangesOverlap(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) {
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
right > UINT64_MAX - right_size) {
EXIT("invalid image page-overlap range\n");
}
const auto left_first = left / TRACKER_PAGE_SIZE;
const auto left_last = (left + left_size - 1) / TRACKER_PAGE_SIZE;
const auto right_first = right / TRACKER_PAGE_SIZE;
const auto right_last = (right + right_size - 1) / TRACKER_PAGE_SIZE;
return left_first <= right_last && right_first <= left_last;
}
[[nodiscard]] inline SampledOverlap ClassifySampledOverlap(const ImageInfo& requested,
const ImageInfo& cached,
bool cached_gpu_modified) {
if (!ImagePageRangesOverlap(requested.address, requested.size, cached.address, cached.size)) {
return SampledOverlap::None;
}
return !cached_gpu_modified ? SampledOverlap::ReadOnlyAlias : SampledOverlap::Unsupported;
}
[[nodiscard]] inline bool IsRgba8SrgbReinterpretation(vk::Format cached,
vk::Format requested) noexcept;
[[nodiscard]] inline bool IsR32UintFloatReinterpretation(vk::Format cached,
vk::Format requested) noexcept {
switch (cached) {
case vk::Format::eR32Uint: return requested == vk::Format::eR32Sfloat;
case vk::Format::eR32Sfloat: return requested == vk::Format::eR32Uint;
default: return false;
}
}
[[nodiscard]] inline bool IsRgba16UintFloatReinterpretation(vk::Format cached,
vk::Format requested) noexcept {
switch (cached) {
case vk::Format::eR16G16B16A16Sfloat: return requested == vk::Format::eR16G16B16A16Uint;
case vk::Format::eR16G16B16A16Uint: return requested == vk::Format::eR16G16B16A16Sfloat;
default: return false;
}
}
[[nodiscard]] inline bool IsRgba8UnormUintReinterpretation(vk::Format cached,
vk::Format requested) noexcept {
switch (cached) {
case vk::Format::eR8G8B8A8Unorm: return requested == vk::Format::eR8G8B8A8Uint;
case vk::Format::eR8G8B8A8Uint: return requested == vk::Format::eR8G8B8A8Unorm;
default: return false;
}
}
[[nodiscard]] inline bool IsSampledDepthExpansion(const ImageInfo& sampled,
const DepthTargetInfo& target) noexcept {
const bool array_expansion =
sampled.type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) &&
sampled.depth > 1 && target.size <= UINT64_MAX / sampled.depth &&
sampled.size == target.size * sampled.depth && sampled.width == target.width &&
sampled.height == target.height && sampled.pitch == target.pitch;
return sampled.address == target.address && sampled.size > target.size &&
sampled.format == target.guest_format && target.format == vk::Format::eD32Sfloat &&
target.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
target.bytes_per_element == 4 && target.stencil_address == 0 &&
target.stencil_size == 0 && target.htile_address == 0 && target.htile_size == 0 &&
target.layers == 1 && array_expansion && sampled.base_level == 0 &&
sampled.levels == 1 && sampled.view_levels == 1 && sampled.tile == target.tile_mode &&
sampled.base_array == 0;
}
[[nodiscard]] inline StorageSampledOverlap ClassifyStorageSampledOverlap(
const ImageInfo& requested, const ImageInfo& cached, vk::Format requested_view_format,
vk::Format cached_image_format, bool cached_gpu_modified, bool cached_cpu_dirty,
bool exact_mip_subresource = false, bool cached_buffer_modified = false,
bool tracker_gpu_modified = true) {
if (!ImagePageRangesOverlap(requested.address, requested.size, cached.address, cached.size)) {
return StorageSampledOverlap::None;
}
const bool same_backing =
requested.address == cached.address && requested.size == cached.size &&
requested.width == cached.width && requested.height == cached.height &&
requested.pitch == cached.pitch && requested.base_level == cached.base_level &&
requested.levels == cached.levels && requested.view_levels == cached.view_levels &&
requested.tile == cached.tile && requested.depth == cached.depth &&
requested.type == cached.type && requested.base_array == cached.base_array;
const bool compatible_format =
(requested.format == cached.format && requested_view_format == cached_image_format) ||
IsRgba8SrgbReinterpretation(cached_image_format, requested_view_format) ||
IsR32UintFloatReinterpretation(cached_image_format, requested_view_format);
if (same_backing && compatible_format && cached_gpu_modified && !cached_cpu_dirty) {
return StorageSampledOverlap::ExactImage;
}
if (exact_mip_subresource && cached_gpu_modified && tracker_gpu_modified &&
!cached_buffer_modified && !cached_cpu_dirty) {
return StorageSampledOverlap::RetireStorage;
}
return StorageSampledOverlap::Unsupported;
}
[[nodiscard]] inline HostWriteOverlap
ClassifyHostWriteOverlap(uint64_t write_address, uint64_t write_size, uint64_t image_address,
uint64_t image_size, bool host_refreshable, bool gpu_modified,
bool metadata_overlap) {
if (!ImagePageRangesOverlap(write_address, write_size, image_address, image_size)) {
return HostWriteOverlap::None;
}
return host_refreshable && !gpu_modified && !metadata_overlap
? HostWriteOverlap::InvalidateImage
: HostWriteOverlap::Unsupported;
}
[[nodiscard]] inline BufferImageWrite
ClassifyBufferImageWrite(uint64_t buffer_address, uint64_t buffer_size, uint64_t image_address,
uint64_t image_size, BufferImageBinding binding, bool image_gpu_modified,
bool buffer_formatted, bool image_buffer_modified = false) {
if (!ImagePageRangesOverlap(buffer_address, buffer_size, image_address, image_size)) {
return BufferImageWrite::None;
}
const bool exact = buffer_address == image_address && buffer_size == image_size;
const auto offset =
buffer_address >= image_address ? buffer_address - image_address : UINT64_MAX;
const bool contained = offset <= image_size && buffer_size <= image_size - offset;
const auto image_offset =
image_address >= buffer_address ? image_address - buffer_address : UINT64_MAX;
const bool image_contained =
image_offset <= buffer_size && image_size <= buffer_size - image_offset;
const bool buffer_page_aligned =
((buffer_address | buffer_size) & (TRACKER_PAGE_SIZE - 1)) == 0;
const bool image_page_aligned = ((image_address | image_size) & (TRACKER_PAGE_SIZE - 1)) == 0;
switch (binding) {
case BufferImageBinding::Texture:
return contained && image_page_aligned && !image_gpu_modified
? BufferImageWrite::InvalidateTexture
: BufferImageWrite::Unsupported;
case BufferImageBinding::VideoOut:
if (!exact || !buffer_page_aligned || !buffer_formatted) {
return BufferImageWrite::Unsupported;
}
return image_gpu_modified ? BufferImageWrite::SynchronizeVideoOut
: BufferImageWrite::InvalidateVideoOut;
case BufferImageBinding::RenderTarget:
if (!exact || !buffer_page_aligned || !buffer_formatted) {
return BufferImageWrite::Unsupported;
}
if (image_gpu_modified && !image_buffer_modified) {
return BufferImageWrite::SynchronizeRenderTarget;
}
return !image_gpu_modified && image_buffer_modified
? BufferImageWrite::InvalidateRenderTarget
: BufferImageWrite::Unsupported;
case BufferImageBinding::StorageTexture:
if (!buffer_page_aligned || !image_page_aligned || !buffer_formatted) {
return BufferImageWrite::Unsupported;
}
if (contained && image_gpu_modified && !image_buffer_modified) {
return BufferImageWrite::SynchronizeStorageTexture;
}
return image_contained && !image_gpu_modified && image_buffer_modified
? BufferImageWrite::InvalidateStorageTexture
: BufferImageWrite::Unsupported;
case BufferImageBinding::DepthTarget:
if (!exact || !buffer_page_aligned || !buffer_formatted) {
return BufferImageWrite::Unsupported;
}
if (image_gpu_modified && !image_buffer_modified) {
return BufferImageWrite::SynchronizeDepthTarget;
}
return !image_gpu_modified && image_buffer_modified
? BufferImageWrite::InvalidateDepthTarget
: BufferImageWrite::Unsupported;
case BufferImageBinding::Unsupported: return BufferImageWrite::Unsupported;
}
return BufferImageWrite::Unsupported;
}
[[nodiscard]] inline StorageBufferRebind
ClassifyStorageBufferRebind(bool buffer_overlap, bool cached_gpu_modified,
bool cached_buffer_modified, bool tracker_gpu_modified,
bool tracker_cpu_modified, bool coherent_guest_source) noexcept {
if (cached_gpu_modified != tracker_gpu_modified ||
(tracker_gpu_modified && tracker_cpu_modified)) {
return StorageBufferRebind::Unsupported;
}
if (!cached_buffer_modified) {
return StorageBufferRebind::Reuse;
}
return buffer_overlap && !tracker_gpu_modified && coherent_guest_source
? StorageBufferRebind::RefreshFromBacking
: StorageBufferRebind::Unsupported;
}
[[nodiscard]] inline DepthOverlap ClassifyDepthOverlap(const ImageInfo& sampled,
bool sampled_gpu_modified,
const DepthTargetInfo& depth) {
const bool depth_overlap =
ImageRangeOverlaps(sampled.address, sampled.size, depth.address, depth.size);
const bool stencil_overlap =
depth.stencil_address != 0 && ImageRangeOverlaps(sampled.address, sampled.size,
depth.stencil_address, depth.stencil_size);
if (!depth_overlap && !stencil_overlap) {
return DepthOverlap::None;
}
const bool has_stencil = depth.stencil_address != 0 || depth.stencil_size != 0;
const auto* depth_policy = FindGuestDepthFormatPolicy(depth.guest_format);
const bool exact_depth_format =
sampled.format == depth.guest_format && depth_policy != nullptr &&
depth.bytes_per_element == depth_policy->bytes_per_element &&
DepthAspectTransferBytes(depth.format) == depth.bytes_per_element &&
(has_stencil ? IsStencilAttachmentFormat(*depth_policy, depth.format)
: depth.format == depth_policy->depth_attachment_format);
// Keep this equivalent to ResolveDepthOverlap: recreate the sampled color image as
// depth and preserve its depth aspect. A disjoint stencil component is initialized separately.
const bool exact_depth_load =
!stencil_overlap && !depth.depth_load_clear && sampled.address == depth.address &&
sampled.size == depth.size && sampled.width == depth.width &&
sampled.height == depth.height && sampled.pitch == depth.pitch && sampled.base_level == 0 &&
sampled.levels == 1 && sampled.view_levels == 1 && sampled.tile == depth.tile_mode &&
sampled.depth == 1 &&
sampled.type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
depth.layers == 1 && sampled.base_array == 0 && exact_depth_format;
if (!sampled_gpu_modified && exact_depth_load) {
return DepthOverlap::RetireSampled;
}
if (sampled.address == depth.address && !sampled_gpu_modified && depth.depth_load_clear &&
(!has_stencil ||
(depth.stencil_address != 0 && depth.stencil_size != 0 && depth.stencil_load_clear))) {
return DepthOverlap::RetireSampled;
}
return DepthOverlap::Unsupported;
}
// Mirrors storage -> depth-target binding transition. An inaccessible stencil aspect
// does not require the otherwise necessary image content copy.
[[nodiscard]] inline DepthOverlap
ClassifyStorageDepthOverlap(const ImageInfo& storage, bool storage_gpu_modified,
bool storage_buffer_modified, bool storage_cpu_dirty,
bool tracker_gpu_modified, const DepthTargetInfo& depth) {
const bool overlaps_depth =
ImageRangeOverlaps(storage.address, storage.size, depth.address, depth.size);
const bool overlaps_stencil =
depth.stencil_address != 0 && ImageRangeOverlaps(storage.address, storage.size,
depth.stencil_address, depth.stencil_size);
if (!overlaps_depth && !overlaps_stencil) {
return DepthOverlap::None;
}
const bool guest_current = HasGuestCurrentImageOwnership(
storage_gpu_modified, storage_buffer_modified, storage_cpu_dirty, tracker_gpu_modified);
const bool aspects_discarded =
(!overlaps_depth || !depth.depth_access) && (!overlaps_stencil || !depth.stencil_access);
return guest_current || (!storage_buffer_modified && !storage_cpu_dirty && aspects_discarded)
? DepthOverlap::RetireStorage
: DepthOverlap::Unsupported;
}
[[nodiscard]] inline bool
CanRetireGuestCurrentDepthForSampled(const ImageInfo& sampled, const DepthTargetInfo& depth,
bool depth_gpu_modified, bool depth_buffer_modified,
bool depth_tracker_gpu_modified,
bool stencil_tracker_gpu_modified) noexcept {
const bool overlaps_depth =
ImageRangeOverlaps(sampled.address, sampled.size, depth.address, depth.size);
const bool overlaps_stencil =
depth.stencil_address != 0 && ImageRangeOverlaps(sampled.address, sampled.size,
depth.stencil_address, depth.stencil_size);
return (overlaps_depth || overlaps_stencil) &&
HasGuestCurrentImageOwnership(depth_gpu_modified, depth_buffer_modified, false,
depth_tracker_gpu_modified ||
stencil_tracker_gpu_modified);
}
[[nodiscard]] inline bool
CanRetireGuestCurrentSampledForDepth(const ImageInfo& sampled, const DepthTargetInfo& depth,
bool sampled_gpu_modified, bool sampled_buffer_modified,
bool sampled_cpu_dirty, bool tracker_gpu_modified,
bool guest_source_current) noexcept {
const bool overlaps_depth =
ImageRangeOverlaps(sampled.address, sampled.size, depth.address, depth.size);
const bool overlaps_stencil =
depth.stencil_address != 0 && ImageRangeOverlaps(sampled.address, sampled.size,
depth.stencil_address, depth.stencil_size);
return (overlaps_depth || overlaps_stencil) && guest_source_current &&
HasGuestCurrentImageOwnership(sampled_gpu_modified, sampled_buffer_modified,
sampled_cpu_dirty, tracker_gpu_modified);
}
[[nodiscard]] inline RenderTargetOverlap
ClassifyRenderTargetOverlap(const ImageInfo& sampled, bool sampled_gpu_modified,
const RenderTargetInfo& target) {
if (!ImagePageRangesOverlap(sampled.address, sampled.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
if (!ImageRangeOverlaps(sampled.address, sampled.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
return !sampled_gpu_modified ? RenderTargetOverlap::RetireSampled
: RenderTargetOverlap::Unsupported;
}
[[nodiscard]] inline RenderTargetOverlap
ClassifyStorageRenderTargetOverlap(const ImageInfo& storage, vk::Format storage_format,
bool storage_gpu_modified, bool storage_buffer_modified,
bool storage_cpu_dirty, bool tracker_gpu_modified,
const RenderTargetInfo& target) {
if (!ImagePageRangesOverlap(storage.address, storage.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
if (!ImageRangeOverlaps(storage.address, storage.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
const bool exact_native_image =
storage.address == target.address && storage.size == target.size &&
storage_format == target.format && storage.width == target.width &&
storage.height == target.height && storage.pitch == target.pitch &&
storage.base_level == 0 && storage.levels == 1 && storage.view_levels == 1 &&
storage.tile == target.tile_mode && storage.depth == 1 &&
storage.type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
storage.base_array == 0 && target.levels == 1 && target.layers == 1 && target.samples == 1;
if (exact_native_image && storage_gpu_modified && tracker_gpu_modified &&
!storage_buffer_modified && !storage_cpu_dirty) {
return RenderTargetOverlap::PreserveStorage;
}
return HasGuestCurrentImageOwnership(storage_gpu_modified, storage_buffer_modified,
storage_cpu_dirty, tracker_gpu_modified)
? RenderTargetOverlap::RetireStorage
: RenderTargetOverlap::Unsupported;
}
[[nodiscard]] inline bool IsRgba8SrgbViewFormat(vk::Format format) noexcept {
return format == vk::Format::eR8G8B8A8Unorm || format == vk::Format::eR8G8B8A8Srgb ||
format == vk::Format::eB8G8R8A8Unorm || format == vk::Format::eB8G8R8A8Srgb;
}
[[nodiscard]] inline bool IsRgba8SrgbReinterpretation(vk::Format cached,
vk::Format requested) noexcept {
switch (cached) {
case vk::Format::eR8G8B8A8Unorm: return requested == vk::Format::eR8G8B8A8Srgb;
case vk::Format::eR8G8B8A8Srgb: return requested == vk::Format::eR8G8B8A8Unorm;
case vk::Format::eB8G8R8A8Unorm: return requested == vk::Format::eB8G8R8A8Srgb;
case vk::Format::eB8G8R8A8Srgb: return requested == vk::Format::eB8G8R8A8Unorm;
default: return false;
}
}
[[nodiscard]] inline bool IsCompatibleRenderTargetView(const RenderTargetInfo& cached,
const RenderTargetInfo& requested) noexcept {
return cached.address == requested.address && cached.size == requested.size &&
cached.width == requested.width && cached.height == requested.height &&
cached.pitch == requested.pitch &&
cached.bytes_per_element == requested.bytes_per_element &&
cached.tile_mode == requested.tile_mode && cached.levels == requested.levels &&
cached.layers == requested.layers && cached.samples == requested.samples &&
IsRgba8SrgbReinterpretation(cached.format, requested.format);
}
[[nodiscard]] inline RenderTargetOverlap
ClassifySampledRenderTargetOverlap(const ImageInfo& sampled, const RenderTargetInfo& target,
bool target_buffer_modified) {
if (!ImagePageRangesOverlap(sampled.address, sampled.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
if (!ImageRangeOverlaps(sampled.address, sampled.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
return !target_buffer_modified ? RenderTargetOverlap::RetireTarget
: RenderTargetOverlap::Unsupported;
}
[[nodiscard]] inline StorageImageOverlap
ClassifyStorageImageOverlap(uint64_t requested_address, uint64_t requested_size,
uint64_t cached_address, uint64_t cached_size, bool sampled,
bool gpu_modified, bool buffer_modified, bool tracker_gpu_modified) {
if (!ImagePageRangesOverlap(requested_address, requested_size, cached_address, cached_size)) {
return StorageImageOverlap::None;
}
if (!ImageRangeOverlaps(requested_address, requested_size, cached_address, cached_size)) {
return StorageImageOverlap::PageNeighbor;
}
return sampled && !gpu_modified && !buffer_modified && !tracker_gpu_modified
? StorageImageOverlap::RetireSampled
: StorageImageOverlap::Unsupported;
}
[[nodiscard]] inline constexpr bool LayeredBackingContains(uint64_t container_size,
uint32_t container_layers,
uint64_t view_size,
uint32_t view_layers) {
return container_layers >= view_layers && container_layers != 0 && view_layers != 0 &&
container_size % container_layers == 0 && view_size % view_layers == 0 &&
container_size / container_layers == view_size / view_layers;
}
[[nodiscard]] inline constexpr bool
LayeredPlaneContains(uint64_t container_address, uint64_t container_size, uint32_t container_layers,
uint64_t view_address, uint64_t view_size, uint32_t view_layers) {
return container_address == 0 ? view_address == 0 && view_size == 0
: view_address == container_address &&
LayeredBackingContains(container_size, container_layers,
view_size, view_layers);
}
[[nodiscard]] inline bool
IsCompatibleRenderTargetBacking(const RenderTargetInfo& cached,
const RenderTargetInfo& requested) noexcept {
return cached.address == requested.address &&
LayeredBackingContains(cached.size, cached.layers, requested.size, requested.layers) &&
cached.width == requested.width && cached.height == requested.height &&
cached.pitch == requested.pitch &&
cached.bytes_per_element == requested.bytes_per_element &&
cached.tile_mode == requested.tile_mode && cached.levels == requested.levels &&
cached.samples == requested.samples &&
(cached.format == requested.format ||
IsRgba8SrgbReinterpretation(cached.format, requested.format));
}
[[nodiscard]] inline bool
IsCompatibleDepthTargetBacking(const DepthTargetInfo& cached,
const DepthTargetInfo& requested) noexcept {
return cached.address == requested.address &&
LayeredBackingContains(cached.size, cached.layers, requested.size, requested.layers) &&
LayeredPlaneContains(cached.stencil_address, cached.stencil_size, cached.layers,
requested.stencil_address, requested.stencil_size,
requested.layers) &&
LayeredPlaneContains(cached.htile_address, cached.htile_size, cached.layers,
requested.htile_address, requested.htile_size, requested.layers) &&
cached.format == requested.format && cached.guest_format == requested.guest_format &&
cached.width == requested.width && cached.height == requested.height &&
cached.pitch == requested.pitch &&
cached.bytes_per_element == requested.bytes_per_element &&
cached.tile_mode == requested.tile_mode && cached.samples == requested.samples &&
cached.stencil_htile_compressed == requested.stencil_htile_compressed;
}
[[nodiscard]] inline RenderTargetOverlap
ClassifyRenderTargetOverlap(const RenderTargetInfo& cached, bool cached_gpu_modified,
bool cached_buffer_modified, bool tracker_gpu_modified,
bool guest_source_current, const RenderTargetInfo& requested) {
if (!ImagePageRangesOverlap(cached.address, cached.size, requested.address, requested.size)) {
return RenderTargetOverlap::None;
}
if (!ImageRangeOverlaps(cached.address, cached.size, requested.address, requested.size)) {
return RenderTargetOverlap::None;
}
const bool expand = requested.layers > cached.layers &&
IsCompatibleRenderTargetBacking(requested, cached) &&
cached.format == requested.format;
if (expand && cached_gpu_modified && tracker_gpu_modified && !cached_buffer_modified) {
return RenderTargetOverlap::ExpandTarget;
}
// A clean, page-isolated overlap is allocation-pool reuse, including a contained subrange.
// Compatible views are handled before this classifier and are never retired here.
const bool page_isolated =
cached.address % TRACKER_PAGE_SIZE == 0 && cached.size % TRACKER_PAGE_SIZE == 0 &&
requested.address % TRACKER_PAGE_SIZE == 0 && requested.size % TRACKER_PAGE_SIZE == 0;
const bool incompatible_format = cached.format != requested.format &&
!IsRgba8SrgbReinterpretation(cached.format, requested.format);
const bool pool_storage_shape_changed =
incompatible_format || cached.width != requested.width ||
cached.height != requested.height || cached.pitch != requested.pitch ||
cached.bytes_per_element != requested.bytes_per_element ||
cached.tile_mode != requested.tile_mode || cached.levels != requested.levels ||
cached.layers != requested.layers || cached.samples != requested.samples;
const bool new_allocation = cached.address != requested.address ||
cached.size != requested.size || pool_storage_shape_changed;
return page_isolated && new_allocation && guest_source_current &&
HasGuestCurrentImageOwnership(cached_gpu_modified, cached_buffer_modified, false,
tracker_gpu_modified)
? RenderTargetOverlap::RetireTarget
: RenderTargetOverlap::Unsupported;
}
[[nodiscard]] inline DepthOverlap ClassifyDepthTargetOverlap(const DepthTargetInfo& cached,
bool cached_gpu_modified,
bool cached_buffer_modified,
const DepthTargetInfo& requested) {
const auto overlaps = [](uint64_t left, uint64_t left_size, uint64_t right, uint64_t right_size,
bool pages) {
if (left == 0 || left_size == 0 || right == 0 || right_size == 0) {
return false;
}
return pages ? ImagePageRangesOverlap(left, left_size, right, right_size)
: ImageRangeOverlaps(left, left_size, right, right_size);
};
const auto planes_overlap = [&](bool pages) {
return overlaps(cached.address, cached.size, requested.address, requested.size, pages) ||
overlaps(cached.address, cached.size, requested.stencil_address,
requested.stencil_size, pages) ||
overlaps(cached.stencil_address, cached.stencil_size, requested.address,
requested.size, pages) ||
overlaps(cached.stencil_address, cached.stencil_size, requested.stencil_address,
requested.stencil_size, pages);
};
if (!planes_overlap(true) || !planes_overlap(false)) {
return DepthOverlap::None;
}
const bool expand =
requested.layers > cached.layers && IsCompatibleDepthTargetBacking(requested, cached);
if (expand && cached_gpu_modified && !cached_buffer_modified) {
return DepthOverlap::ExpandTarget;
}
const bool exact_discard =
requested.depth_load_clear && cached_gpu_modified && !cached_buffer_modified &&
cached.address == requested.address && cached.size == requested.size &&
cached.samples == requested.samples && cached.stencil_address == 0 &&
cached.stencil_size == 0 && cached.htile_address == 0 && cached.htile_size == 0 &&
requested.stencil_address == 0 && requested.stencil_size == 0 &&
requested.htile_address == 0 && requested.htile_size == 0;
if (exact_discard) {
return DepthOverlap::DiscardTarget;
}
const bool exact_plane_rebind =
(cached.address == requested.address && cached.size == requested.size) ||
(cached.address == requested.stencil_address && cached.size == requested.stencil_size) ||
(cached.stencil_address == requested.address && cached.stencil_size == requested.size) ||
(cached.stencil_address == requested.stencil_address &&
cached.stencil_size == requested.stencil_size);
const bool same_shape = cached.format == requested.format &&
cached.guest_format == requested.guest_format &&
cached.width == requested.width && cached.height == requested.height &&
cached.pitch == requested.pitch &&
cached.bytes_per_element == requested.bytes_per_element &&
cached.tile_mode == requested.tile_mode && cached.layers == 1 &&
requested.layers == 1 && cached.samples == 1 && requested.samples == 1;
return exact_plane_rebind && same_shape && !cached_buffer_modified
? DepthOverlap::RecreateTarget
: DepthOverlap::Unsupported;
}
[[nodiscard]] inline bool CanRecreateDepthForRenderTarget(const DepthTargetInfo& depth,
bool gpu_modified, bool buffer_modified,
bool tracker_gpu_modified,
bool guest_source_current,
const RenderTargetInfo& target) noexcept {
const bool exact_depth = depth.address == target.address && depth.size == target.size;
const bool exact_stencil =
depth.stencil_address == target.address && depth.stencil_size == target.size;
const auto contains = [&](uint64_t address, uint64_t size) {
if (address == 0 || size == 0 || address < target.address) {
return false;
}
const auto offset = address - target.address;
return offset <= target.size && size <= target.size - offset;
};
const bool page_isolated_rebind =
target.address % TRACKER_PAGE_SIZE == 0 && target.size % TRACKER_PAGE_SIZE == 0 &&
((contains(depth.address, depth.size) && depth.address % TRACKER_PAGE_SIZE == 0 &&
depth.size % TRACKER_PAGE_SIZE == 0) ||
(contains(depth.stencil_address, depth.stencil_size) &&
depth.stencil_address % TRACKER_PAGE_SIZE == 0 &&
depth.stencil_size % TRACKER_PAGE_SIZE == 0));
const bool ownership_consistent = gpu_modified == tracker_gpu_modified && !buffer_modified;
const bool source_available =
((exact_depth || exact_stencil) && gpu_modified) || guest_source_current;
return ownership_consistent && source_available &&
(exact_depth || exact_stencil || page_isolated_rebind) && depth.layers == 1 &&
target.levels == 1 && target.layers == 1 && depth.samples == 1 && target.samples == 1;
}
[[nodiscard]] inline bool CanRecreateRenderTargetForDepth(const RenderTargetInfo& target,
bool gpu_modified, bool buffer_modified,
bool tracker_gpu_modified,
bool guest_source_current,
const DepthTargetInfo& depth) noexcept {
const bool exact_depth = target.address == depth.address && target.size == depth.size;
const bool exact_stencil =
target.address == depth.stencil_address && target.size == depth.stencil_size;
const auto overlaps_plane = [&](uint64_t address, uint64_t size) {
return address != 0 && size != 0 &&
ImageRangeOverlaps(target.address, target.size, address, size);
};
const bool page_isolated_rebind =
target.address % TRACKER_PAGE_SIZE == 0 && target.size % TRACKER_PAGE_SIZE == 0 &&
((overlaps_plane(depth.address, depth.size) && depth.address % TRACKER_PAGE_SIZE == 0 &&
depth.size % TRACKER_PAGE_SIZE == 0) ||
(overlaps_plane(depth.stencil_address, depth.stencil_size) &&
depth.stencil_address % TRACKER_PAGE_SIZE == 0 &&
depth.stencil_size % TRACKER_PAGE_SIZE == 0));
const bool source_available =
(gpu_modified && (exact_depth || exact_stencil)) || guest_source_current;
return gpu_modified == tracker_gpu_modified && !buffer_modified && source_available &&
(exact_depth || exact_stencil || page_isolated_rebind) && target.levels == 1 &&
target.layers == 1 && depth.layers == 1 && target.samples == 1 && depth.samples == 1;
}
[[nodiscard]] inline bool RequiresMultisampleDepthRefresh(const DepthTargetInfo& info,
bool buffer_modified,
bool depth_cpu_modified,
bool stencil_cpu_modified) noexcept {
if (info.samples == 1) {
return false;
}
const bool depth_refresh =
info.depth_access && !info.depth_load_clear && (buffer_modified || depth_cpu_modified);
const bool stencil_refresh = info.stencil_access && !info.stencil_load_clear &&
(buffer_modified || stencil_cpu_modified);
return depth_refresh || stencil_refresh;
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_