This commit is contained in:
Arthur Cuesta 2026-06-26 11:45:58 +02:00 committed by GitHub
commit 703f38d555
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 309 additions and 69 deletions

View File

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <vector>
#include <sirit/sirit.h>
#include "shader_recompiler/backend/spirv/emit_spirv_quad_rect.h"
#include "shader_recompiler/runtime_info.h"
@ -12,8 +15,17 @@ using Sirit::Id;
constexpr u32 SPIRV_VERSION_1_5 = 0x00010500;
struct QuadRectListEmitter : public Sirit::Module {
explicit QuadRectListEmitter(const FragmentRuntimeInfo& fs_info_)
: Sirit::Module{SPIRV_VERSION_1_5}, fs_info{fs_info_} {
struct AuxParam {
Id input;
Id output;
u32 location{};
bool has_input{};
};
explicit QuadRectListEmitter(const FragmentRuntimeInfo& fs_info_,
u64 previous_stage_output_mask_)
: Sirit::Module{SPIRV_VERSION_1_5}, fs_info{fs_info_},
previous_stage_output_mask{previous_stage_output_mask_} {
void_id = TypeVoid();
bool_id = TypeBool();
float_id = TypeFloat(32);
@ -24,9 +36,11 @@ struct QuadRectListEmitter : public Sirit::Module {
vec3_id = TypeVector(float_id, 3);
vec4_id = TypeVector(float_id, 4);
float_zero = Constant(float_id, 0.0f);
float_one = Constant(float_id, 1.0f);
float_min_one = Constant(float_id, -1.0f);
int_zero = Constant(int_id, 0);
vec4_zero = ConstantComposite(vec4_id, float_zero, float_zero, float_zero, float_zero);
const Id float_arr{TypeArray(float_id, Constant(uint_id, 1U))};
gl_per_vertex_type = TypeStruct(vec4_id, float_id, float_arr, float_arr);
@ -115,17 +129,24 @@ struct QuadRectListEmitter : public Sirit::Module {
const Id position{OpSelect(vec4_id, invocation_3, pos3, OpLoad(vec4_id, in_ptr))};
OpStore(OpAccessChain(output_vec4, gl_out, invocation_id, Int(0)), position);
// Set attributes
for (int i = 0; i < inputs.size(); i++) {
// vec4 in_paramN3 = interpolate(bary_coord, in_paramN[0], in_paramN[1], in_paramN[2]);
const Id v0{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], Int(0)))};
const Id v1{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], Int(1)))};
const Id v2{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], Int(2)))};
const Id in_param3{interpolate(v0, v1, v2)};
// out_paramN[gl_InvocationID] = gl_InvocationID == 3 ? in_paramN3 : in_paramN[index];
const Id in_param{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], index))};
const Id out_param{OpSelect(vec4_id, invocation_3, in_param3, in_param)};
OpStore(OpAccessChain(output_vec4, outputs[i], invocation_id), out_param);
// Set attributes. If a fragment input has no corresponding VS output, do not declare a
// TCS input for it; synthesize a neutral value instead. This keeps the VS->TCS interface
// valid while still producing a matching TCS->TES->FS interface.
for (const auto& param : params) {
Id out_param = vec4_zero;
if (param.has_input) {
// vec4 in_paramN3 = interpolate(bary_coord, in_paramN[0], in_paramN[1],
// in_paramN[2]);
const Id v0{OpLoad(vec4_id, OpAccessChain(input_vec4, param.input, Int(0)))};
const Id v1{OpLoad(vec4_id, OpAccessChain(input_vec4, param.input, Int(1)))};
const Id v2{OpLoad(vec4_id, OpAccessChain(input_vec4, param.input, Int(2)))};
const Id in_param3{interpolate(v0, v1, v2)};
// out_paramN[gl_InvocationID] = gl_InvocationID == 3 ? in_paramN3 :
// in_paramN[index];
const Id in_param{OpLoad(vec4_id, OpAccessChain(input_vec4, param.input, index))};
out_param = OpSelect(vec4_id, invocation_3, in_param3, in_param);
}
OpStore(OpAccessChain(output_vec4, param.output, invocation_id), out_param);
}
OpReturn();
@ -161,10 +182,14 @@ struct QuadRectListEmitter : public Sirit::Module {
const Id in_position{OpLoad(vec4_id, OpAccessChain(input_vec4, gl_in, index, Int(0)))};
OpStore(OpAccessChain(output_vec4, gl_out, invocation_id, Int(0)), in_position);
for (int i = 0; i < inputs.size(); i++) {
// out_paramN[gl_InvocationID] = in_paramN[gl_InvocationID];
const Id in_param{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], index))};
OpStore(OpAccessChain(output_vec4, outputs[i], invocation_id), in_param);
for (const auto& param : params) {
// out_paramN[gl_InvocationID] = in_paramN[gl_InvocationID], or zero if the
// preceding VS does not export this location.
Id in_param = vec4_zero;
if (param.has_input) {
in_param = OpLoad(vec4_id, OpAccessChain(input_vec4, param.input, index));
}
OpStore(OpAccessChain(output_vec4, param.output, invocation_id), in_param);
}
OpReturn();
@ -189,9 +214,9 @@ struct QuadRectListEmitter : public Sirit::Module {
OpStore(OpAccessChain(output_vec4, gl_per_vertex, Int(0)), position);
// out_paramN = in_paramN[index];
for (int i = 0; i < inputs.size(); i++) {
const Id param{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], index))};
OpStore(outputs[i], param);
for (const auto& param : params) {
const Id value{OpLoad(vec4_id, OpAccessChain(input_vec4, param.input, index))};
OpStore(param.output, value);
}
OpReturn();
@ -235,6 +260,29 @@ private:
AddLabel(OpLabel());
}
u32 GetLocation(const FragmentRuntimeInfo::PsInput& input) const {
return AuxTessAttributeLocation(input.param_index, fs_info.clip_distance_emulation);
}
bool IsPreviousStageOutputAvailable(u32 location) const {
return location < 64u && ((previous_stage_output_mask >> location) & 1ull) != 0;
}
void AddClipDistanceParam(spv::ExecutionModel model) {
if (!fs_info.clip_distance_emulation) {
return;
}
AuxParam& param = params.emplace_back();
param.location = 0;
if (model == spv::ExecutionModel::TessellationEvaluation ||
IsPreviousStageOutputAvailable(param.location)) {
const Id float_arr{TypeArray(vec4_id, Int(32))};
param.input = AddInput(float_arr);
param.has_input = true;
Decorate(param.input, spv::Decoration::Location, param.location);
}
}
void DefineOutputs(spv::ExecutionModel model) {
if (model == spv::ExecutionModel::TessellationControl) {
const Id gl_per_vertex_array{TypeArray(gl_per_vertex_type, Constant(uint_id, 4U))};
@ -252,16 +300,12 @@ private:
} else {
gl_per_vertex = AddOutput(gl_per_vertex_type);
}
outputs.reserve(fs_info.num_inputs);
for (int i = 0; i < fs_info.num_inputs; i++) {
const auto& input = fs_info.inputs[i];
if (input.IsDefault()) {
continue;
}
outputs.emplace_back(AddOutput(model == spv::ExecutionModel::TessellationControl
? TypeArray(vec4_id, Int(4))
: vec4_id));
Decorate(outputs.back(), spv::Decoration::Location, input.param_index);
for (auto& param : params) {
param.output = AddOutput(model == spv::ExecutionModel::TessellationControl
? TypeArray(vec4_id, Int(4))
: vec4_id);
Decorate(param.output, spv::Decoration::Location, param.location);
}
}
@ -275,20 +319,30 @@ private:
}
const Id gl_per_vertex_array{TypeArray(gl_per_vertex_type, Constant(uint_id, 32U))};
gl_in = AddInput(gl_per_vertex_array);
params.reserve(fs_info.num_inputs + (fs_info.clip_distance_emulation ? 1u : 0u));
AddClipDistanceParam(model);
const Id float_arr{TypeArray(vec4_id, Int(32))};
inputs.reserve(fs_info.num_inputs);
for (int i = 0; i < fs_info.num_inputs; i++) {
const auto& input = fs_info.inputs[i];
if (input.IsDefault()) {
continue;
}
inputs.emplace_back(AddInput(float_arr));
Decorate(inputs.back(), spv::Decoration::Location, input.param_index);
AuxParam& param = params.emplace_back();
param.location = GetLocation(input);
if (model == spv::ExecutionModel::TessellationEvaluation ||
IsPreviousStageOutputAvailable(param.location)) {
param.input = AddInput(float_arr);
param.has_input = true;
Decorate(param.input, spv::Decoration::Location, param.location);
}
}
}
private:
FragmentRuntimeInfo fs_info;
u64 previous_stage_output_mask;
Id main;
Id void_id;
Id bool_id;
@ -299,9 +353,11 @@ private:
Id vec2_id;
Id vec3_id;
Id vec4_id;
Id float_zero;
Id float_one;
Id float_min_one;
Id int_zero;
Id vec4_zero;
Id gl_per_vertex_type;
Id gl_in;
union {
@ -314,13 +370,13 @@ private:
Id gl_tess_coord;
Id gl_invocation_id;
};
std::vector<Id> inputs;
std::vector<Id> outputs;
std::vector<AuxParam> params;
std::vector<Id> interfaces;
};
std::vector<u32> EmitAuxilaryTessShader(AuxShaderType type, const FragmentRuntimeInfo& fs_info) {
QuadRectListEmitter ctx{fs_info};
std::vector<u32> EmitAuxilaryTessShader(AuxShaderType type, const FragmentRuntimeInfo& fs_info,
u64 previous_stage_output_mask) {
QuadRectListEmitter ctx{fs_info, previous_stage_output_mask};
switch (type) {
case AuxShaderType::RectListTCS:
ctx.EmitRectListTCS();

View File

@ -18,7 +18,13 @@ enum class AuxShaderType : u32 {
PassthroughTES,
};
[[nodiscard]] constexpr u32 AuxTessAttributeLocation(u32 param_index,
bool clip_distance_emulation) noexcept {
return param_index + (clip_distance_emulation ? 1u : 0u);
}
[[nodiscard]] std::vector<u32> EmitAuxilaryTessShader(AuxShaderType type,
const FragmentRuntimeInfo& fs_info);
const FragmentRuntimeInfo& fs_info,
u64 previous_stage_output_mask = ~0ull);
} // namespace Shader::Backend::SPIRV

View File

@ -46,13 +46,15 @@ struct BufferSpecialization {
struct ImageSpecialization {
AmdGpu::ImageType type = AmdGpu::ImageType::Color2D;
bool is_integer = false;
AmdGpu::NumberClass num_class = AmdGpu::NumberClass::Float;
AmdGpu::DataFormat data_format = AmdGpu::DataFormat::FormatInvalid;
AmdGpu::NumberFormat num_format = AmdGpu::NumberFormat::Unorm;
bool is_storage = false;
bool is_cube = false;
bool is_srgb = false;
bool is_null = false;
AmdGpu::CompMapping dst_select{};
AmdGpu::NumberConversion num_conversion{};
// FIXME any pipeline cache changes needed?
u32 num_bindings = 0;
bool operator==(const ImageSpecialization&) const = default;
@ -134,20 +136,30 @@ struct StageSpecialization {
spec.element_size = sharp.element_size;
}
});
ForEachSharp(binding, images, info->images,
[&](auto& spec, const auto& desc, AmdGpu::Image sharp) {
spec.type = sharp.GetViewType(desc.is_array);
spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt());
spec.is_storage = desc.is_written;
spec.is_cube = sharp.IsCube();
if (spec.is_storage) {
spec.dst_select = sharp.DstSelect();
} else {
spec.is_srgb = sharp.GetNumberFmt() == AmdGpu::NumberFormat::Srgb;
}
spec.num_conversion = sharp.GetNumberConversion();
spec.num_bindings = desc.NumBindings(*info);
});
for (const auto& desc : info->images) {
auto& spec = images.emplace_back();
const auto sharp = desc.GetSharp(*info);
if (!sharp.Valid() || sharp.GetDataFmt() == AmdGpu::DataFormat::FormatInvalid) {
binding++;
continue;
}
bitset.set(binding++);
const auto num_format = sharp.GetNumberFmt();
spec.type = sharp.GetViewType(desc.is_array);
spec.num_class = AmdGpu::GetNumberClass(num_format);
spec.data_format = sharp.GetDataFmt();
spec.num_format = num_format;
spec.is_storage = desc.is_written;
spec.is_cube = sharp.IsCube();
spec.is_null = !sharp;
if (spec.is_storage) {
spec.dst_select = sharp.DstSelect();
} else {
spec.is_srgb = num_format == AmdGpu::NumberFormat::Srgb;
}
spec.num_conversion = sharp.GetNumberConversion();
spec.num_bindings = desc.NumBindings(*info);
}
ForEachSharp(binding, fmasks, info->fmasks,
[](auto& spec, const auto& desc, AmdGpu::Image sharp) {
spec.width = sharp.width;
@ -229,6 +241,10 @@ struct StageSpecialization {
return false;
}
if (bitset != other.bitset) {
return false;
}
u32 binding{};
for (u32 i = 0; i < buffers.size(); i++) {
if (other.bitset[binding++] && buffers[i] != other.buffers[i]) {

View File

@ -7,6 +7,8 @@
#include "common/assert.h"
#include "shader_recompiler/backend/spirv/emit_spirv_quad_rect.h"
#include "shader_recompiler/info.h"
#include "shader_recompiler/ir/attribute.h"
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_instance.h"
@ -17,6 +19,53 @@ namespace Vulkan {
using Shader::Backend::SPIRV::AuxShaderType;
namespace {
u64 BuildAuxTessPreviousStageOutputMask(const Shader::Info* vs_info,
const Shader::FragmentRuntimeInfo& fs_info) {
if (!vs_info) {
return 0;
}
u64 mask = 0;
if (fs_info.clip_distance_emulation &&
vs_info->stores.GetAny(Shader::IR::Attribute::ClipDistance)) {
mask |= 1ull;
}
for (u32 i = 0; i < Shader::IR::NumParams; ++i) {
const auto param = Shader::IR::Attribute::Param0 + static_cast<int>(i);
if (!vs_info->stores.GetAny(param)) {
continue;
}
const u32 location =
Shader::Backend::SPIRV::AuxTessAttributeLocation(i, fs_info.clip_distance_emulation);
if (location < 64u) {
mask |= 1ull << location;
}
}
return mask;
}
void LogAuxTessMissingInputs(const Shader::FragmentRuntimeInfo& fs_info, u64 output_mask) {
for (u32 i = 0; i < fs_info.num_inputs; ++i) {
const auto& input = fs_info.inputs[i];
if (input.IsDefault()) {
continue;
}
const u32 location = Shader::Backend::SPIRV::AuxTessAttributeLocation(
input.param_index, fs_info.clip_distance_emulation);
if (location < 64u && ((output_mask >> location) & 1ull) == 0) {
LOG_DEBUG(Render_Vulkan,
"Aux tess input location {} for PS input {} has no VS output; "
"synthesizing zero",
location, i);
}
}
}
} // Anonymous namespace
static constexpr std::array LogicalStageToStageBit = {
vk::ShaderStageFlagBits::eFragment,
vk::ShaderStageFlagBits::eTessellationControl,
@ -194,7 +243,12 @@ GraphicsPipeline::GraphicsPipeline(
const auto type = is_quad_list ? AuxShaderType::QuadListTCS : AuxShaderType::RectListTCS;
if (!preloading) {
const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info;
sdata.tcs = Shader::Backend::SPIRV::EmitAuxilaryTessShader(type, fs_info);
const auto* vs_info = infos[u32(Shader::LogicalStage::Vertex)];
const u64 previous_stage_output_mask =
BuildAuxTessPreviousStageOutputMask(vs_info, fs_info);
LogAuxTessMissingInputs(fs_info, previous_stage_output_mask);
sdata.tcs = Shader::Backend::SPIRV::EmitAuxilaryTessShader(type, fs_info,
previous_stage_output_mask);
}
shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{
.stage = vk::ShaderStageFlagBits::eTessellationControl,

View File

@ -12,9 +12,9 @@
namespace Serialization {
/* You should increment versions below once corresponding serialization scheme is changed. */
static constexpr u32 ShaderBinaryVersion = 2u;
static constexpr u32 ShaderMetaVersion = 2u;
static constexpr u32 PipelineKeyVersion = 2u;
static constexpr u32 ShaderBinaryVersion = 3u;
static constexpr u32 ShaderMetaVersion = 3u;
static constexpr u32 PipelineKeyVersion = 3u;
} // namespace Serialization
namespace Vulkan {

View File

@ -20,6 +20,104 @@
namespace Vulkan {
namespace {
enum class VkFormatNumberClass {
Float,
Sint,
Uint,
Unknown,
};
constexpr const char* NameOf(VkFormatNumberClass num_class) noexcept {
switch (num_class) {
case VkFormatNumberClass::Float:
return "Float";
case VkFormatNumberClass::Sint:
return "Sint";
case VkFormatNumberClass::Uint:
return "Uint";
case VkFormatNumberClass::Unknown:
return "Unknown";
}
return "Unknown";
}
constexpr VkFormatNumberClass GetFormatNumberClass(vk::Format format) noexcept {
switch (format) {
case vk::Format::eR8Uint:
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eB8G8R8Uint:
case vk::Format::eB8G8R8A8Uint:
case vk::Format::eA8B8G8R8UintPack32:
case vk::Format::eR16Uint:
case vk::Format::eR16G16Uint:
case vk::Format::eR16G16B16Uint:
case vk::Format::eR16G16B16A16Uint:
case vk::Format::eR32Uint:
case vk::Format::eR32G32Uint:
case vk::Format::eR32G32B32Uint:
case vk::Format::eR32G32B32A32Uint:
case vk::Format::eR64Uint:
case vk::Format::eR64G64Uint:
case vk::Format::eR64G64B64Uint:
case vk::Format::eR64G64B64A64Uint:
case vk::Format::eA2B10G10R10UintPack32:
case vk::Format::eA2R10G10B10UintPack32:
case vk::Format::eS8Uint:
return VkFormatNumberClass::Uint;
case vk::Format::eR8Sint:
case vk::Format::eR8G8Sint:
case vk::Format::eR8G8B8Sint:
case vk::Format::eR8G8B8A8Sint:
case vk::Format::eB8G8R8Sint:
case vk::Format::eB8G8R8A8Sint:
case vk::Format::eA8B8G8R8SintPack32:
case vk::Format::eR16Sint:
case vk::Format::eR16G16Sint:
case vk::Format::eR16G16B16Sint:
case vk::Format::eR16G16B16A16Sint:
case vk::Format::eR32Sint:
case vk::Format::eR32G32Sint:
case vk::Format::eR32G32B32Sint:
case vk::Format::eR32G32B32A32Sint:
case vk::Format::eR64Sint:
case vk::Format::eR64G64Sint:
case vk::Format::eR64G64B64Sint:
case vk::Format::eR64G64B64A64Sint:
case vk::Format::eA2B10G10R10SintPack32:
case vk::Format::eA2R10G10B10SintPack32:
return VkFormatNumberClass::Sint;
case vk::Format::eUndefined:
return VkFormatNumberClass::Unknown;
default:
return VkFormatNumberClass::Float;
}
}
void LogImageViewNumberClassMismatch(const Shader::Info& stage,
const VideoCore::TextureCache::ImageDesc& desc,
const VideoCore::ImageView& image_view,
VideoCore::ImageId image_id) {
const auto expected = GetFormatNumberClass(desc.view_info.format);
const auto actual = GetFormatNumberClass(image_view.format);
if (expected == VkFormatNumberClass::Unknown || actual == VkFormatNumberClass::Unknown ||
expected == actual) {
return;
}
LOG_ERROR(Render_Vulkan,
"Image numeric type mismatch before descriptor write: stage={} image_id={} "
"expected={} requested_format={} actual={} actual_format={} storage={}",
stage.stage, image_id.index, NameOf(expected), vk::to_string(desc.view_info.format),
NameOf(actual), vk::to_string(image_view.format),
desc.type == VideoCore::TextureCache::BindingType::Storage);
}
} // Anonymous namespace
static Shader::PushData MakeUserData(const AmdGpu::Regs& regs) {
// TODO(roamic): Add support for multiple viewports and geometry shaders when ViewportIndex
// is encountered and implemented in the recompiler.
@ -742,7 +840,12 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin
if (instance.IsNullDescriptorSupported()) {
image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral);
} else {
auto& null_image_view = texture_cache.FindTexture(VideoCore::NULL_IMAGE_ID, desc);
const auto null_format = desc.view_info.format != vk::Format::eUndefined
? desc.view_info.format
: desc.info.pixel_format;
const auto null_image_id = texture_cache.GetNullImage(null_format);
auto& null_image_view = texture_cache.FindTexture(null_image_id, desc);
LogImageViewNumberClassMismatch(stage, desc, null_image_view, null_image_id);
image_infos.emplace_back(VK_NULL_HANDLE, *null_image_view.image_view,
vk::ImageLayout::eGeneral);
}
@ -757,6 +860,7 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin
auto& image = texture_cache.GetImage(image_id);
auto& image_view = texture_cache.FindTexture(image_id, desc);
LogImageViewNumberClassMismatch(stage, desc, image_view, image_id);
// The image is either bound as storage in a separate descriptor or bound as render
// target in feedback loop. Depth images are excluded because they can't be bound as

View File

@ -109,11 +109,12 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info
aspect = vk::ImageAspectFlagBits::eStencil;
}
this->format = instance.GetSupportedFormat(format, image.format_features);
const vk::ImageViewCreateInfo image_view_ci = {
.pNext = &usage_ci,
.image = image.GetImage(),
.viewType = ConvertImageViewType(info.type),
.format = instance.GetSupportedFormat(format, image.format_features),
.format = this->format,
.components = info.mapping,
.subresourceRange{
.aspectMask = aspect,
@ -136,12 +137,14 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info
const auto view_aspect = aspect & vk::ImageAspectFlagBits::eDepth ? "Depth"
: aspect & vk::ImageAspectFlagBits::eStencil ? "Stencil"
: "Color";
Vulkan::SetObjectName(
instance.GetDevice(), *image_view, "ImageView {}x{}x{} {:#x}:{:#x} {}:{} {}:{} ({})",
image.info.size.width, image.info.size.height, image.info.size.depth,
image.info.guest_address, image.info.guest_size, info.range.base.level,
info.range.base.level + info.range.extent.levels - 1, info.range.base.layer,
info.range.base.layer + info.range.extent.layers - 1, view_aspect);
Vulkan::SetObjectName(instance.GetDevice(), *image_view,
"ImageView {}x{}x{} {:#x}:{:#x} {}:{} {}:{} ({}) req:{} view:{}",
image.info.size.width, image.info.size.height, image.info.size.depth,
image.info.guest_address, image.info.guest_size, info.range.base.level,
info.range.base.level + info.range.extent.levels - 1,
info.range.base.layer,
info.range.base.layer + info.range.extent.layers - 1, view_aspect,
vk::to_string(info.format), vk::to_string(this->format));
}
ImageView::~ImageView() = default;

View File

@ -52,6 +52,7 @@ struct ImageView {
ImageView& operator=(ImageView&&) = default;
ImageViewInfo info;
vk::Format format = vk::Format::eUndefined;
vk::UniqueImageView image_view;
};

View File

@ -105,6 +105,9 @@ public:
/// Retrieves an image view with the properties of the specified image id.
[[nodiscard]] ImageView& FindTexture(ImageId image_id, const ImageDesc& desc);
/// Gets or creates a null image for a particular Vulkan format.
[[nodiscard]] ImageId GetNullImage(vk::Format format);
/// Retrieves the render target with specified properties
[[nodiscard]] ImageView& FindRenderTarget(ImageId image_id, const ImageDesc& desc);
@ -268,9 +271,6 @@ private:
}
}
/// Gets or creates a null image for a particular format.
ImageId GetNullImage(vk::Format format);
/// Copies image memory back to CPU.
void DownloadImageMemory(ImageId image_id);