mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
1219 lines
57 KiB
C++
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_
|