video_core: Fix a few problems

This commit is contained in:
IndecisiveTurtle 2024-06-29 16:44:39 +03:00 committed by TheTurtle
parent 114f06d3f2
commit ad10020836
10 changed files with 89 additions and 49 deletions

View File

@ -1260,6 +1260,10 @@ int PS4_SYSV_ABI posix_sem_post(sem_t* sem) {
return sem_post(sem);
}
int PS4_SYSV_ABI posix_sem_getvalue(sem_t* sem, int* sval) {
return sem_getvalue(sem, sval);
}
int PS4_SYSV_ABI scePthreadGetschedparam(ScePthread thread, int* policy,
SceKernelSchedParam* param) {
return pthread_getschedparam(thread->pth, policy, param);
@ -1379,6 +1383,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("pDuPEf3m4fI", "libScePosix", 1, "libkernel", 1, 1, posix_sem_init);
LIB_FUNCTION("YCV5dGGBcCo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_wait);
LIB_FUNCTION("IKP8typ0QUk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_post);
LIB_FUNCTION("Bq+LRV-N6Hk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_getvalue);
// libs
RwlockSymbolsRegister(sym);
SemaphoreSymbolsRegister(sym);

View File

@ -328,6 +328,7 @@ void Translate(IR::Block* block, std::span<const GcnInst> inst_list, Info& info)
translator.V_FMA_F32(inst);
break;
case Opcode::IMAGE_SAMPLE_LZ_O:
case Opcode::IMAGE_SAMPLE_O:
case Opcode::IMAGE_SAMPLE_C_LZ:
case Opcode::IMAGE_SAMPLE_LZ:
case Opcode::IMAGE_SAMPLE:
@ -455,6 +456,7 @@ void Translate(IR::Block* block, std::span<const GcnInst> inst_list, Info& info)
translator.BUFFER_LOAD_FORMAT(4, false, inst);
break;
case Opcode::BUFFER_STORE_FORMAT_X:
case Opcode::BUFFER_STORE_DWORD:
translator.BUFFER_STORE_FORMAT(1, false, inst);
break;
case Opcode::BUFFER_STORE_FORMAT_XYZW:
@ -469,6 +471,9 @@ void Translate(IR::Block* block, std::span<const GcnInst> inst_list, Info& info)
case Opcode::V_MAX_U32:
translator.V_MAX_U32(false, inst);
break;
case Opcode::V_NOT_B32:
translator.V_NOT_B32(inst);
break;
case Opcode::V_RSQ_F32:
translator.V_RSQ_F32(inst);
break;

View File

@ -129,6 +129,7 @@ public:
void V_MIN_U32(const GcnInst& inst);
void V_CMP_NE_U64(const GcnInst& inst);
void V_BFI_B32(const GcnInst& inst);
void V_NOT_B32(const GcnInst& inst);
// Vector Memory
void BUFFER_LOAD_FORMAT(u32 num_dwords, bool is_typed, const GcnInst& inst);

View File

@ -46,7 +46,10 @@ void Translator::V_CNDMASK_B32(const GcnInst& inst) {
const bool has_flt_source =
is_float_const(inst.src[0].field) || is_float_const(inst.src[1].field);
const IR::U32F32 src0 = GetSrc(inst.src[0], has_flt_source);
const IR::U32F32 src1 = GetSrc(inst.src[1], has_flt_source);
IR::U32F32 src1 = GetSrc(inst.src[1], has_flt_source);
if (src0.Type() == IR::Type::F32 && src1.Type() == IR::Type::U32) {
src1 = ir.BitCast<IR::F32, IR::U32>(src1);
}
const IR::Value result = ir.Select(flag, src1, src0);
ir.SetVectorReg(dst_reg, IR::U32F32{result});
}
@ -478,4 +481,9 @@ void Translator::V_BFI_B32(const GcnInst& inst) {
ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2)));
}
void Translator::V_NOT_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
SetDst(inst.dst[0], ir.BitwiseNot(src0));
}
} // namespace Shader::Gcn

View File

@ -9,7 +9,7 @@
namespace Shader::IR {
namespace {
[[noreturn]] void ThrowInvalidType(Type type) {
throw InvalidArgument("Invalid type {}", u32(type));
UNREACHABLE_MSG("Invalid type {}", u32(type));
}
Value MakeLodClampPair(IREmitter& ir, const F32& bias_lod, const F32& lod_clamp) {
@ -251,7 +251,7 @@ U32U64 IREmitter::ReadShared(int bit_size, bool is_signed, const U32& offset) {
case 64:
return Inst<U64>(Opcode::ReadSharedU64, offset);
}
throw InvalidArgument("Invalid bit size {}", bit_size);*/
UNREACHABLE_MSG("Invalid bit size {}", bit_size);*/
}
void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) {
@ -269,7 +269,7 @@ void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset)
Inst(Opcode::WriteSharedU64, offset, value);
break;
default:
throw InvalidArgument("Invalid bit size {}", bit_size);
UNREACHABLE_MSG("Invalid bit size {}", bit_size);
}*/
}
@ -293,7 +293,7 @@ Value IREmitter::LoadBuffer(int num_dwords, const Value& handle, const Value& ad
case 4:
return Inst(Opcode::LoadBufferF32x4, Flags{info}, handle, address);
default:
throw InvalidArgument("Invalid number of dwords {}", num_dwords);
UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords);
}
}
@ -314,7 +314,7 @@ void IREmitter::StoreBuffer(int num_dwords, const Value& handle, const Value& ad
Inst(Opcode::StoreBufferF32x4, Flags{info}, handle, address, data);
break;
default:
throw InvalidArgument("Invalid number of dwords {}", num_dwords);
UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords);
}
}
@ -328,7 +328,7 @@ U32 IREmitter::QuadShuffle(const U32& value, const U32& index) {
F32F64 IREmitter::FPAdd(const F32F64& a, const F32F64& b) {
if (a.Type() != b.Type()) {
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type());
}
switch (a.Type()) {
case Type::F32:
@ -342,7 +342,7 @@ F32F64 IREmitter::FPAdd(const F32F64& a, const F32F64& b) {
F32F64 IREmitter::FPSub(const F32F64& a, const F32F64& b) {
if (a.Type() != b.Type()) {
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type());
}
switch (a.Type()) {
case Type::F32:
@ -354,7 +354,7 @@ F32F64 IREmitter::FPSub(const F32F64& a, const F32F64& b) {
Value IREmitter::CompositeConstruct(const Value& e1, const Value& e2) {
if (e1.Type() != e2.Type()) {
throw InvalidArgument("Mismatching types {} and {}", e1.Type(), e2.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", e1.Type(), e2.Type());
}
switch (e1.Type()) {
case Type::U32:
@ -372,7 +372,7 @@ Value IREmitter::CompositeConstruct(const Value& e1, const Value& e2) {
Value IREmitter::CompositeConstruct(const Value& e1, const Value& e2, const Value& e3) {
if (e1.Type() != e2.Type() || e1.Type() != e3.Type()) {
throw InvalidArgument("Mismatching types {}, {}, and {}", e1.Type(), e2.Type(), e3.Type());
UNREACHABLE_MSG("Mismatching types {}, {}, and {}", e1.Type(), e2.Type(), e3.Type());
}
switch (e1.Type()) {
case Type::U32:
@ -391,7 +391,7 @@ Value IREmitter::CompositeConstruct(const Value& e1, const Value& e2, const Valu
Value IREmitter::CompositeConstruct(const Value& e1, const Value& e2, const Value& e3,
const Value& e4) {
if (e1.Type() != e2.Type() || e1.Type() != e3.Type() || e1.Type() != e4.Type()) {
throw InvalidArgument("Mismatching types {}, {}, {}, and {}", e1.Type(), e2.Type(),
UNREACHABLE_MSG("Mismatching types {}, {}, {}, and {}", e1.Type(), e2.Type(),
e3.Type(), e4.Type());
}
switch (e1.Type()) {
@ -411,7 +411,7 @@ Value IREmitter::CompositeConstruct(const Value& e1, const Value& e2, const Valu
Value IREmitter::CompositeExtract(const Value& vector, size_t element) {
const auto read{[&](Opcode opcode, size_t limit) -> Value {
if (element >= limit) {
throw InvalidArgument("Out of bounds element {}", element);
UNREACHABLE_MSG("Out of bounds element {}", element);
}
return Inst(opcode, vector, Value{static_cast<u32>(element)});
}};
@ -448,7 +448,7 @@ Value IREmitter::CompositeExtract(const Value& vector, size_t element) {
Value IREmitter::CompositeInsert(const Value& vector, const Value& object, size_t element) {
const auto insert{[&](Opcode opcode, size_t limit) {
if (element >= limit) {
throw InvalidArgument("Out of bounds element {}", element);
UNREACHABLE_MSG("Out of bounds element {}", element);
}
return Inst(opcode, vector, object, Value{static_cast<u32>(element)});
}};
@ -484,7 +484,7 @@ Value IREmitter::CompositeInsert(const Value& vector, const Value& object, size_
Value IREmitter::Select(const U1& condition, const Value& true_value, const Value& false_value) {
if (true_value.Type() != false_value.Type()) {
throw InvalidArgument("Mismatching types {} and {}", true_value.Type(), false_value.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", true_value.Type(), false_value.Type());
}
switch (true_value.Type()) {
case Type::U1:
@ -502,7 +502,7 @@ Value IREmitter::Select(const U1& condition, const Value& true_value, const Valu
case Type::F64:
return Inst(Opcode::SelectF64, condition, true_value, false_value);
default:
throw InvalidArgument("Invalid type {}", true_value.Type());
UNREACHABLE_MSG("Invalid type {}", true_value.Type());
}
}
@ -532,7 +532,7 @@ Value IREmitter::UnpackHalf2x16(const U32& value) {
F32F64 IREmitter::FPMul(const F32F64& a, const F32F64& b) {
if (a.Type() != b.Type()) {
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type());
}
switch (a.Type()) {
case Type::F32:
@ -546,7 +546,7 @@ F32F64 IREmitter::FPMul(const F32F64& a, const F32F64& b) {
F32F64 IREmitter::FPFma(const F32F64& a, const F32F64& b, const F32F64& c) {
if (a.Type() != b.Type() || a.Type() != c.Type()) {
throw InvalidArgument("Mismatching types {}, {}, and {}", a.Type(), b.Type(), c.Type());
UNREACHABLE_MSG("Mismatching types {}, {}, and {}", a.Type(), b.Type(), c.Type());
}
switch (a.Type()) {
case Type::F32:
@ -646,7 +646,7 @@ F32F64 IREmitter::FPSaturate(const F32F64& value) {
F32F64 IREmitter::FPClamp(const F32F64& value, const F32F64& min_value, const F32F64& max_value) {
if (value.Type() != min_value.Type() || value.Type() != max_value.Type()) {
throw InvalidArgument("Mismatching types {}, {}, and {}", value.Type(), min_value.Type(),
UNREACHABLE_MSG("Mismatching types {}, {}, and {}", value.Type(), min_value.Type(),
max_value.Type());
}
switch (value.Type()) {
@ -709,7 +709,7 @@ F32 IREmitter::Fract(const F32& value) {
U1 IREmitter::FPEqual(const F32F64& lhs, const F32F64& rhs, bool ordered) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -723,7 +723,7 @@ U1 IREmitter::FPEqual(const F32F64& lhs, const F32F64& rhs, bool ordered) {
U1 IREmitter::FPNotEqual(const F32F64& lhs, const F32F64& rhs, bool ordered) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -737,7 +737,7 @@ U1 IREmitter::FPNotEqual(const F32F64& lhs, const F32F64& rhs, bool ordered) {
U1 IREmitter::FPLessThan(const F32F64& lhs, const F32F64& rhs, bool ordered) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -751,7 +751,7 @@ U1 IREmitter::FPLessThan(const F32F64& lhs, const F32F64& rhs, bool ordered) {
U1 IREmitter::FPGreaterThan(const F32F64& lhs, const F32F64& rhs, bool ordered) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -767,7 +767,7 @@ U1 IREmitter::FPGreaterThan(const F32F64& lhs, const F32F64& rhs, bool ordered)
U1 IREmitter::FPLessThanEqual(const F32F64& lhs, const F32F64& rhs, bool ordered) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -783,7 +783,7 @@ U1 IREmitter::FPLessThanEqual(const F32F64& lhs, const F32F64& rhs, bool ordered
U1 IREmitter::FPGreaterThanEqual(const F32F64& lhs, const F32F64& rhs, bool ordered) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -812,21 +812,21 @@ U1 IREmitter::FPIsNan(const F32F64& value) {
U1 IREmitter::FPOrdered(const F32F64& lhs, const F32F64& rhs) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
return LogicalAnd(LogicalNot(FPIsNan(lhs)), LogicalNot(FPIsNan(rhs)));
}
U1 IREmitter::FPUnordered(const F32F64& lhs, const F32F64& rhs) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
return LogicalOr(FPIsNan(lhs), FPIsNan(rhs));
}
F32F64 IREmitter::FPMax(const F32F64& lhs, const F32F64& rhs) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -840,7 +840,7 @@ F32F64 IREmitter::FPMax(const F32F64& lhs, const F32F64& rhs) {
F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
@ -854,7 +854,7 @@ F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs) {
U32U64 IREmitter::IAdd(const U32U64& a, const U32U64& b) {
if (a.Type() != b.Type()) {
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type());
}
switch (a.Type()) {
case Type::U32:
@ -868,7 +868,7 @@ U32U64 IREmitter::IAdd(const U32U64& a, const U32U64& b) {
U32U64 IREmitter::ISub(const U32U64& a, const U32U64& b) {
if (a.Type() != b.Type()) {
throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type());
}
switch (a.Type()) {
case Type::U32:
@ -1021,7 +1021,7 @@ U1 IREmitter::ILessThan(const U32& lhs, const U32& rhs, bool is_signed) {
U1 IREmitter::IEqual(const U32U64& lhs, const U32U64& rhs) {
if (lhs.Type() != rhs.Type()) {
throw InvalidArgument("Mismatching types {} and {}", lhs.Type(), rhs.Type());
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::U32:
@ -1075,7 +1075,7 @@ U32U64 IREmitter::ConvertFToS(size_t bitsize, const F32F64& value) {
ThrowInvalidType(value.Type());
}
default:
throw InvalidArgument("Invalid destination bitsize {}", bitsize);
UNREACHABLE_MSG("Invalid destination bitsize {}", bitsize);
}
}
@ -1089,7 +1089,7 @@ U32U64 IREmitter::ConvertFToU(size_t bitsize, const F32F64& value) {
ThrowInvalidType(value.Type());
}
default:
throw InvalidArgument("Invalid destination bitsize {}", bitsize);
UNREACHABLE_MSG("Invalid destination bitsize {}", bitsize);
}
}
@ -1112,7 +1112,7 @@ F32F64 IREmitter::ConvertSToF(size_t dest_bitsize, size_t src_bitsize, const Val
}
break;
}
throw InvalidArgument("Invalid bit size combination dst={} src={}", dest_bitsize, src_bitsize);
UNREACHABLE_MSG("Invalid bit size combination dst={} src={}", dest_bitsize, src_bitsize);
}
F32F64 IREmitter::ConvertUToF(size_t dest_bitsize, size_t src_bitsize, const Value& value) {
@ -1130,7 +1130,7 @@ F32F64 IREmitter::ConvertUToF(size_t dest_bitsize, size_t src_bitsize, const Val
}
break;
}
throw InvalidArgument("Invalid bit size combination dst={} src={}", dest_bitsize, src_bitsize);
UNREACHABLE_MSG("Invalid bit size combination dst={} src={}", dest_bitsize, src_bitsize);
}
F32F64 IREmitter::ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_signed,

View File

@ -306,8 +306,13 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
inst.SetArg(0, ir.Imm32(image_binding));
// No need to patch coordinates if we are just querying.
if (inst.GetOpcode() == IR::Opcode::ImageQueryDimensions) {
return;
}
// Now that we know the image type, adjust texture coordinate vector.
const IR::Inst* body = inst.Arg(1).InstRecursive();
IR::Inst* body = inst.Arg(1).InstRecursive();
const auto [coords, arg] = [&] -> std::pair<IR::Value, IR::Value> {
switch (image.GetType()) {
case AmdGpu::ImageType::Color1D: // x

View File

@ -373,6 +373,12 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu
num_format == AmdGpu::NumberFormat::Snorm) {
return vk::Format::eR16G16Snorm;
}
if (data_format == AmdGpu::DataFormat::Format2_10_10_10 && num_format == AmdGpu::NumberFormat::Unorm) {
return vk::Format::eA2R10G10B10UnormPack32;
}
if (data_format == AmdGpu::DataFormat::Format10_11_11 && num_format == AmdGpu::NumberFormat::Float) {
return vk::Format::eB10G11R11UfloatPack32;
}
UNREACHABLE_MSG("Unknown data_format={} and num_format={}", u32(data_format), u32(num_format));
}

View File

@ -207,6 +207,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
inst_pool.ReleaseContents();
// Recompile shader to IR.
try {
LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x}", stage, hash);
const Shader::Info info = MakeShaderInfo(stage, pgm->user_data, regs);
programs[i] = Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info));
@ -216,13 +217,16 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
stages[i] = CompileSPV(spv_code, instance.GetDevice());
infos[i] = &programs[i].info;
// Set module name to hash in renderdoc
const auto name = fmt::format("{}_{:#x}", stage, hash);
Vulkan::SetObjectName(instance.GetDevice(), stages[i], name);
if (Config::dumpShaders()) {
DumpShader(spv_code, hash, stage, "spv");
}
} catch (const Shader::Exception& e) {
UNREACHABLE_MSG("{}", e.what());
}
// Set module name to hash in renderdoc
const auto name = fmt::format("{}_{:#x}", stage, hash);
Vulkan::SetObjectName(instance.GetDevice(), stages[i], name);
}
return std::make_unique<GraphicsPipeline>(instance, scheduler, graphics_key, *pipeline_cache,

View File

@ -91,7 +91,7 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) {
// TODO: Don't restart renderpass every draw
const auto& scissor = regs.screen_scissor;
const vk::RenderingInfo rendering_info = {
vk::RenderingInfo rendering_info = {
.renderArea =
{
.offset = {scissor.top_left_x, scissor.top_left_y},
@ -102,6 +102,11 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) {
.pColorAttachments = color_attachments.data(),
.pDepthAttachment = num_depth_attachments ? &depth_attachment : nullptr,
};
auto& area = rendering_info.renderArea.extent;
if (area.width == 2048) {
area.width = 1920;
area.height = 1080;
}
UpdateDynamicState(*pipeline);

View File

@ -116,7 +116,8 @@ Image& TextureCache::FindImage(const ImageInfo& info, VAddr cpu_address, bool re
std::unique_lock lock{m_page_table};
boost::container::small_vector<ImageId, 2> image_ids;
ForEachImageInRegion(cpu_address, info.guest_size_bytes, [&](ImageId image_id, Image& image) {
if (image.cpu_addr == cpu_address && image.info.size.width == info.size.width) {
if (image.cpu_addr == cpu_address && image.info.size.width == info.size.width &&
image.info.IsDepthStencil() == info.IsDepthStencil()) {
image_ids.push_back(image_id);
}
});