Compare commits

..

6 Commits

Author SHA1 Message Date
nmzik c5016d9455 shader: support D16 XY and XYZ formatted buffer loads and stores 2026-10-22 05:26:54 +02:00
nmzik 753f285198 shader: generalize D16 buffer format component handling 2026-10-22 05:26:54 +02:00
Nawid3333 d7c1896647 shader: interpolate f16 parameter halves (V_INTERP_P1LL_F16, V_INTERP_P2_F16) (#1792)
shader: interpolate packed f16 attributes for v_interp_p1ll_f16 and v_interp_p2_f16

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-22 04:54:10 +02:00
Arda Sahin 8502d8a77d image: drop optional create flags the driver rejects instead of aborting (#1760)
* image: request extended usage only for storage images

* tests: restore block-texel-view support through the flag check loop order

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-22 03:56:13 +02:00
nmzik cb8042c5aa launcher: compress game folders to ZAR 2026-10-22 03:35:38 +02:00
Mr. Big Britches b46eeb7866 audio: stop AudioOut2 contexts from stalling each other (PPSA09809) (#1745)
audio: submit AudioOut2 pushes outside the state mutex so contexts stop stalling each other

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-22 02:52:11 +02:00
23 changed files with 685 additions and 138 deletions
+7
View File
@@ -5,6 +5,7 @@
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <vector>
@@ -45,6 +46,12 @@ private:
// Accepts either a host archive filename or a virtual path inside the archive.
[[nodiscard]] std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path);
// Packs a host directory into a new ZArchive outside it. progress gets 0 < done <= total and
// returns false to cancel. A failed or cancelled pack removes the partial archive.
[[nodiscard]] bool PackArchive(const std::filesystem::path& directory,
const std::filesystem::path& archive,
const std::function<bool(uint64_t done, uint64_t total)>& progress);
} // namespace Common
#endif /* KYTY_COMMON_ARCHIVE_H_ */
+84
View File
@@ -0,0 +1,84 @@
// Kept out of archive.cpp so the statically linked emulator does not pull in the writer and the
// zstd compressor.
#include "common/archive.h"
#include "common/stringUtils.h"
#include <algorithm>
#include <zarchive/zarchivewriter.h>
namespace Common {
bool PackArchive(const std::filesystem::path& directory, const std::filesystem::path& archive,
const std::function<bool(uint64_t done, uint64_t total)>& progress) {
namespace fs = std::filesystem;
struct Output {
File file;
bool ok = true;
} output;
// Create the archive before walking, so a re-run started while a cancelled pack is still
// walking sees it exists instead of losing its archive to that pack's cleanup.
if (!output.file.Create(archive)) {
return false;
}
struct Entry {
fs::path path;
uint64_t size;
bool is_file;
};
std::vector<Entry> entries;
uint64_t total = 0;
std::error_code error;
// Follow directory links, as the emulator does when it runs the folder.
fs::recursive_directory_iterator it(directory, fs::directory_options::follow_directory_symlink,
error);
for (; !error && it != fs::end(it); it.increment(error)) {
const bool is_file = !it->is_directory(error);
const auto size = is_file && !error ? it->file_size(error) : 0;
if (error) {
break;
}
entries.push_back({it->path(), size, is_file});
total += size;
}
output.ok = !error;
ZArchiveWriter writer(
[](int32_t, void*) {},
[](const void* data, size_t size, void* opaque) {
auto& out = *static_cast<Output*>(opaque);
uint32_t written = 0;
out.file.Write(data, static_cast<uint32_t>(size), &written);
out.ok = out.ok && written == size;
},
&output);
std::vector<uint8_t> buffer(1u << 20u);
uint64_t done = 0;
for (size_t index = 0; output.ok && index < entries.size(); index++) {
const auto& entry = entries[index];
const auto name = PathToGenericString(entry.path.lexically_relative(directory));
File input;
output.ok = entry.is_file ? writer.StartNewFile(name.c_str()) &&
input.Open(entry.path, File::Mode::Read)
: writer.MakeDir(name.c_str());
for (uint64_t left = entry.size; output.ok && left != 0;) {
uint32_t read = 0;
input.Read(buffer.data(), static_cast<uint32_t>(std::min<uint64_t>(left, buffer.size())),
&read);
writer.AppendData(buffer.data(), read);
done += read;
left -= read;
output.ok = output.ok && read != 0 && progress(done, total);
}
}
if (output.ok) {
writer.Finalize();
}
output.ok = output.ok && output.file.Flush();
output.file.Close();
if (!output.ok) {
File::DeleteFile(archive);
}
return output.ok;
}
} // namespace Common
@@ -30,16 +30,18 @@ namespace {
}
}
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const GraphicContext& graphics,
const ImageInfo& info) {
[[nodiscard]] vk::ImageCreateFlags
ImageCreateFlags(const GraphicContext& graphics, const ImageInfo& info, vk::ImageUsageFlags usage) {
vk::ImageCreateFlags flags {};
if (DepthAspectTransferFormat(info.pixel_format) == vk::Format::eUndefined) {
flags |= vk::ImageCreateFlagBits::eMutableFormat;
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
if (info.IsBlock() && graphics.supports_block_texel_view) {
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
}
}
if (usage & vk::ImageUsageFlagBits::eStorage) {
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
}
if (info.IsVolume()) {
flags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
}
@@ -59,7 +61,7 @@ namespace {
const auto storage = usage | vk::ImageUsageFlagBits::eStorage;
if (graphics.GetImageFormatProperties(info.pixel_format, HostImageType(info.type),
vk::ImageTiling::eOptimal, storage,
ImageCreateFlags(graphics, info),
ImageCreateFlags(graphics, info, storage),
nullptr) == vk::Result::eSuccess) {
usage = storage;
} else {
@@ -702,7 +704,8 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
}
vk::ImageCreateInfo create {};
create.flags = ImageCreateFlags(graphics, info);
create.usage = ImageUsageFlags(graphics, info);
create.flags = ImageCreateFlags(graphics, info, create.usage);
create.imageType = HostImageType(info.type);
create.extent = info.extent;
create.mipLevels = info.resources.levels;
@@ -710,7 +713,6 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
create.format = info.pixel_format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = ImageUsageFlags(graphics, info);
create.samples = vulkan_sample_count(info.samples);
vk::ImageFormatProperties properties {};
@@ -4,6 +4,7 @@
#include <algorithm>
#include <atomic>
#include <optional>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
@@ -140,6 +141,74 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
return writable;
}
uint32_t LoadVertexParameter(EmitterState& state, const InputBinding& input, uint32_t vertex,
uint32_t chan) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
input.variable_id, ConstantU32(state, vertex),
ConstantU32(state, chan & 3u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
return value;
}
// Interpolates a pixel input channel to f32. A half (0 low, 1 high) selects one f16 of a packed
// FP16_INTERP_MODE dword before blending; smooth inputs read by halves are always per-vertex.
uint32_t EmitPixelAttribute(EmitterState& state, const InputBinding& input, uint32_t attr,
uint32_t chan, std::optional<uint32_t> half) {
const auto select = [&](uint32_t value) {
if (!half) {
return value;
}
const auto bits = state.builder.AllocateId();
const auto unpacked = state.builder.AllocateId();
const auto word = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked,
GlslStd450(state), GLSLstd450UnpackHalf2x16, bits);
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), word, unpacked, *half);
return word;
};
if (!input.per_vertex) {
const auto vector = state.builder.AllocateId();
const auto component = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input.variable_id);
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector,
chan & 3u);
return select(component);
}
if (PixelParameterIsFlat(state, attr)) {
const auto vertex =
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
return select(LoadVertexParameter(state, input, vertex, chan));
}
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
? IR::StageInputKind::BaryCoordNoPerspective
: IR::StageInputKind::BaryCoordSmooth;
const auto barycentric = InputVariableForKind(state, barycentric_kind);
uint32_t sum = 0;
for (uint32_t vertex = 0; vertex < 3u; vertex++) {
const auto pointer = state.builder.AllocateId();
const auto weight = state.builder.AllocateId();
const auto product = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, barycentric, ConstantU32(state, vertex));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer);
state.builder.AddFunction(spv::OpFMul, TypeF32(state), product,
select(LoadVertexParameter(state, input, vertex, chan)), weight);
if (vertex == 0u) {
sum = product;
} else {
const auto next = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product);
sum = next;
}
}
return sum;
}
uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
const auto* input = InputBindingForParameter(state, attr);
if (input == nullptr || input->variable_id == 0) {
@@ -148,57 +217,9 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) {
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
}
const auto load_per_vertex = [&](uint32_t vertex) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
return value;
};
if (input->per_vertex) {
if (PixelParameterIsFlat(state, attr)) {
const auto value = load_per_vertex(
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u);
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
? IR::StageInputKind::BaryCoordNoPerspective
: IR::StageInputKind::BaryCoordSmooth;
const auto barycentric = InputVariableForKind(state, barycentric_kind);
uint32_t sum = 0;
for (uint32_t vertex = 0; vertex < 3u; vertex++) {
const auto pointer = state.builder.AllocateId();
const auto weight = state.builder.AllocateId();
const auto product = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, barycentric, ConstantU32(state, vertex));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer);
state.builder.AddFunction(spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex),
weight);
if (vertex == 0u) {
sum = product;
} else {
const auto next = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product);
sum = next;
}
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, sum);
return bits;
}
const auto vector = state.builder.AllocateId();
const auto component = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id);
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector,
chan & 3u);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, component);
const auto value = EmitPixelAttribute(state, *input, attr, chan, std::nullopt);
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
@@ -209,25 +230,16 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
if (!input->per_vertex) {
return EmitAttribute(ctx.state, attr, chan);
}
const auto load_vertex = [&](uint32_t vertex) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
return value;
};
uint32_t selected_vertex = (mode + 1u) % 3u;
if (mode == 2u && PixelParameterIsFlat(state, attr)) {
selected_vertex =
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
}
uint32_t value = load_vertex(selected_vertex);
uint32_t value = LoadVertexParameter(state, *input, selected_vertex, chan);
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
const auto delta = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value,
LoadVertexParameter(state, *input, 0, chan));
value = delta;
}
const auto bits = state.builder.AllocateId();
@@ -853,6 +865,12 @@ uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitAttribute(ctx.state, inst.Arg(0).U32(), inst.Arg(1).U32());
}
uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto attr = inst.Arg(0).U32();
return EmitPixelAttribute(ctx.state, *InputBindingForParameter(ctx.state, attr), attr,
inst.Arg(1).U32(), inst.Arg(2).U32());
}
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(), inst.Arg(2).U32());
}
@@ -236,6 +236,7 @@ uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst&
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetShaderBase(ValueEmitContext& ctx);
@@ -6,13 +6,14 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace {
struct MemoryOpcodeInfo {
uint32_t encoding = 0;
Opcode decoded = Opcode::UNSUPPORTED;
uint32_t data_dwords = 1;
uint32_t data_bits = 32;
bool data_signed = false;
bool typed = false;
bool formatted = false;
uint32_t encoding = 0;
Opcode decoded = Opcode::UNSUPPORTED;
uint32_t data_dwords = 1;
uint32_t data_bits = 32;
bool data_signed = false;
bool typed = false;
bool formatted = false;
uint32_t data_components = 0; // D16 formats pack two components per dword
};
constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
@@ -73,9 +74,13 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
{0x72u, Opcode::BUFFER_GL1_INV, 0, 0},
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
{0x81u, Opcode::BUFFER_LOAD_FORMAT_D16_XY, 1, 16, false, false, true, 2},
{0x82u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZ, 2, 16, false, false, true, 3},
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true, 4},
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true},
{0x85u, Opcode::BUFFER_STORE_FORMAT_D16_XY, 1, 16, false, false, true, 2},
{0x86u, Opcode::BUFFER_STORE_FORMAT_D16_XYZ, 2, 16, false, false, true, 3},
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true, 4},
};
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
@@ -158,12 +163,13 @@ void ApplyMemoryInfo(Instruction& inst, const MemoryOpcodeInfo* info) {
inst.opcode = Opcode::UNSUPPORTED;
return;
}
inst.opcode = info->decoded;
inst.data_dwords = info->data_dwords;
inst.data_bits = info->data_bits;
inst.data_signed = info->data_signed;
inst.typed = info->typed;
inst.formatted = info->formatted;
inst.opcode = info->decoded;
inst.data_dwords = info->data_dwords;
inst.data_bits = info->data_bits;
inst.data_signed = info->data_signed;
inst.typed = info->typed;
inst.formatted = info->formatted;
inst.data_components = info->data_components;
}
} // namespace
@@ -323,12 +329,7 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
}
DecodeVectorGpr(vdata, inst.dst);
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_XYZW) {
inst.data_components = 4u;
}
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X ||
inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
if (inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16 ||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI) {
inst.dst.sdwa_sel = inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI ? 5u : 4u;
@@ -636,6 +636,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
case Opcode::BUFFER_LOAD_FORMAT_D16_XY:
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZ:
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
case Opcode::BUFFER_STORE_FORMAT_X:
case Opcode::BUFFER_STORE_FORMAT_XY:
@@ -643,6 +645,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BUFFER_STORE_FORMAT_XYZW:
case Opcode::BUFFER_STORE_FORMAT_D16_X:
case Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
case Opcode::BUFFER_STORE_FORMAT_D16_XY:
case Opcode::BUFFER_STORE_FORMAT_D16_XYZ:
case Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
case Opcode::BUFFER_LOAD_UBYTE:
case Opcode::BUFFER_LOAD_UBYTE_D16:
@@ -547,6 +547,8 @@ enum class Opcode {
BUFFER_LOAD_FORMAT_XYZ,
BUFFER_LOAD_FORMAT_XYZW,
BUFFER_LOAD_FORMAT_D16_X,
BUFFER_LOAD_FORMAT_D16_XY,
BUFFER_LOAD_FORMAT_D16_XYZ,
BUFFER_LOAD_FORMAT_D16_XYZW,
BUFFER_STORE_FORMAT_X,
BUFFER_STORE_FORMAT_XY,
@@ -554,6 +556,8 @@ enum class Opcode {
BUFFER_STORE_FORMAT_XYZW,
BUFFER_STORE_FORMAT_D16_X,
BUFFER_STORE_FORMAT_D16_HI_X,
BUFFER_STORE_FORMAT_D16_XY,
BUFFER_STORE_FORMAT_D16_XYZ,
BUFFER_STORE_FORMAT_D16_XYZW,
BUFFER_LOAD_UBYTE,
BUFFER_LOAD_UBYTE_D16,
@@ -713,6 +717,8 @@ enum class Opcode {
V_INTERP_P1_F32,
V_INTERP_P2_F32,
V_INTERP_MOV_F32,
V_INTERP_P1LL_F16,
V_INTERP_P2_F16,
S_NOP,
S_WAITCNT,
@@ -391,6 +391,8 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x377u, Opcode::V_PERMLANE16_B32},
{0x378u, Opcode::V_PERMLANEX16_B32},
{0x34bu, Opcode::V_FMA_F16},
{0x342u, Opcode::V_INTERP_P1LL_F16},
{0x35au, Opcode::V_INTERP_P2_F16},
{0x36du, Opcode::V_ADD3_U32},
{0x14fu, Opcode::V_ALIGNBYTE_B32},
};
@@ -1553,6 +1555,9 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
}
switch (opcode) {
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
case Opcode::V_INTERP_P1LL_F16:
case Opcode::V_INTERP_P2_F16:
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
case Opcode::V_CNDMASK_B32:
return (abs & ~0x3u) != 0u || op_sel != 0u || clamp != 0u || omod != 0u ||
(neg & ~0x3u) != 0u;
@@ -1664,6 +1669,17 @@ bool IsVopcCompareExec(Opcode opcode) {
}
}
// VINTRP and VOP3 interpolation share one layout: src0 = I/J VGPR, src1 = attr, src2 = chan.
void SetInterpolationAttribute(Instruction& inst, uint32_t attr, uint32_t chan) {
inst.src1.kind = OperandKind::IntegerInlineConstant;
inst.src1.value = attr;
inst.src1.signed_val = static_cast<int32_t>(attr);
inst.src2.kind = OperandKind::IntegerInlineConstant;
inst.src2.value = chan;
inst.src2.signed_val = static_cast<int32_t>(chan);
inst.src_count = 3;
}
} // namespace
void DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
@@ -1830,6 +1846,15 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.src_count = 0;
return;
}
if (inst.opcode == Opcode::V_INTERP_P1LL_F16 || inst.opcode == Opcode::V_INTERP_P2_F16) {
// SRC0 packs {attr_word, attr_chan, attr}; attr_word selects the high f16 of the dword.
DecodeVectorGpr(vdst, inst.dst);
DecodeScalarSource(src1, pc, inst.src0);
SetInterpolationAttribute(inst, src0 & 0x3fu, (src0 >> 6u) & 0x3u);
inst.src1.op_sel = (src0 & 0x100u) != 0u;
inst.dst.sdwa_sel = (op_sel & 0x8u) != 0u ? 5u : 4u;
return;
}
if (compare_exec) {
inst.dst.kind = OperandKind::ExecLo;
} else if (scalar_dst) {
@@ -2004,13 +2029,7 @@ void DecodeVintrp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_ind
} else {
DecodeVectorGpr(vsrc, inst.src0);
}
inst.src1.kind = OperandKind::IntegerInlineConstant;
inst.src1.value = attr;
inst.src1.signed_val = static_cast<int32_t>(attr);
inst.src2.kind = OperandKind::IntegerInlineConstant;
inst.src2.value = chan;
inst.src2.signed_val = static_cast<int32_t>(chan);
inst.src_count = 3;
SetInterpolationAttribute(inst, attr, chan);
}
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
@@ -73,14 +73,19 @@ void Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32
}
}
void Translator::V_INTERP_P1_F32() {}
void Translator::V_INTERP_P2_F32(const Decoder::Instruction& inst) {
const auto value = ir.Emit(IR::ValueOpcode::GetAttribute,
{IR::Value(inst.src1.value), IR::Value(inst.src2.value)});
WriteOperand(inst.dst, value);
}
void Translator::V_INTERP_P2_F16(const Decoder::Instruction& inst) {
const auto value = ir.Emit(IR::ValueOpcode::GetAttributeF16,
{IR::Value(inst.src1.value), IR::Value(inst.src2.value),
IR::Value(uint32_t {inst.src1.op_sel})});
WriteF16(inst.dst, IR::F32(value));
}
void Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst) {
if (inst.src0.value >= 3u) {
EXIT("v_interp_mov_f32 mode %u is reserved at pc 0x%08x", inst.src0.value, inst.pc);
@@ -108,8 +113,11 @@ void Translator::EXP(const Decoder::Instruction& inst) {
void Translator::EmitInterpolation(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return;
// P2 interpolates the whole attribute, so the P1 halves have nothing to compute.
case Decoder::Opcode::V_INTERP_P1_F32:
case Decoder::Opcode::V_INTERP_P1LL_F16: return;
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return;
case Decoder::Opcode::V_INTERP_P2_F16: V_INTERP_P2_F16(inst); return;
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return;
default: return FailMissingTranslation(inst);
}
@@ -455,12 +455,17 @@ void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
AddMemoryInfo(memory, inst.pc));
if (bits != 32u) {
WriteOperand(inst.dst, WidenSubdword(loaded, bits, sign));
} else if (memory.data_dwords == 1u) {
WriteOperand(inst.dst, loaded);
} else {
for (uint32_t component = 0; component < memory.data_dwords; component++) {
WriteOperand(OffsetOperand(inst.dst, component),
ir.CompositeExtract(loaded, component));
return;
}
for (uint32_t component = 0; component < memory.data_dwords; component++) {
const auto dst = OffsetOperand(inst.dst, component);
const auto value =
memory.data_dwords == 1u ? IR::U32(loaded) : ir.CompositeExtract(loaded, component);
// D16 formats with an odd component count keep the high half of their final VGPR.
if (memory.data_bits == 16u && component * 2u + 1u == memory.component_count) {
Write16Bits(dst, value);
} else {
WriteOperand(dst, value);
}
}
}
@@ -992,6 +997,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZ:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
@@ -1012,6 +1019,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XY:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZ:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
@@ -240,8 +240,8 @@ private:
void V_READLANE_B32(const Decoder::Instruction& inst);
void V_WRITELANE_B32(const Decoder::Instruction& inst);
void V_PERMLANE16_B32(const Decoder::Instruction& inst, bool x16);
void V_INTERP_P1_F32();
void V_INTERP_P2_F32(const Decoder::Instruction& inst);
void V_INTERP_P2_F16(const Decoder::Instruction& inst);
void V_INTERP_MOV_F32(const Decoder::Instruction& inst);
void EXP(const Decoder::Instruction& inst);
@@ -6,6 +6,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
using O = Decoder::Opcode;
switch (inst.opcode) {
case O::V_NOP: return;
case O::V_INTERP_P1LL_F16:
case O::V_INTERP_P2_F16: return EmitInterpolation(inst);
case O::V_ADD_I32: ADD_U32(inst, true, false); return;
case O::V_ADDC_U32: ADD_U32(inst, true, true); return;
case O::V_SUB_I32: SUB_U32(inst, true, false); return;
@@ -487,7 +487,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
const auto access = BufferAccessOf(inst.GetOpcode());
const bool packed_formatted =
(access == BufferAccess::Read || access == BufferAccess::Write) && memory.formatted &&
memory.data_bits == 16u && memory.component_count == 4u && buffer_components == 2u;
memory.data_bits == 16u && (memory.component_count + 1u) / 2u == buffer_components;
if (buffer_components > 1u &&
(!vector_buffer || (memory.data_bits != 32u && !packed_formatted) ||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
@@ -338,6 +338,7 @@ VALUE_OPCODE(ImageAtomicXor64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicFMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicFMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(GetAttribute, U32, U32, U32)
VALUE_OPCODE(GetAttributeF16, F32, U32, U32, U32)
VALUE_OPCODE(GetInterpolationParameter, U32, U32, U32, U32)
VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
@@ -373,7 +373,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
}
}
switch (inst.GetOpcode()) {
case ValueOpcode::GetAttribute: {
case ValueOpcode::GetAttribute:
case ValueOpcode::GetAttributeF16: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed attribute reference is not constant");
@@ -395,6 +396,15 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
auto& components = inputs.components[inst.Arg(0).U32()];
components = std::max(components, static_cast<uint8_t>(inst.Arg(1).U32() + 1u));
}
if (inst.GetOpcode() == ValueOpcode::GetAttributeF16) {
if (program.stage != ShaderType::Pixel || !inst.Arg(2).IsImmediate()) {
return Fail("f16 attribute reference is invalid");
}
// FP16_INTERP_MODE packs two halves per dword, so smooth inputs blend unpacked
// vertices; flat inputs unpack the provoking vertex.
const auto input = inst.Arg(0).U32();
inputs.per_vertex[input] |= !ShaderPixelParameterIsFlat(*input_info.pixel, input);
}
break;
}
case ValueOpcode::GetInterpolationParameter: {
@@ -68,6 +68,7 @@ protected slots:
void list_itemDoubleClicked(QTreeWidgetItem* witem, int column);
void open_game_folder();
void remove_save_data();
void compress_game();
void filter_configurations(const QString& text);
private:
@@ -27,6 +27,7 @@
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFutureWatcher>
#include <QHeaderView>
#include <QIcon>
#include <QInputDialog>
@@ -42,6 +43,8 @@
#include <QPalette>
#include <QPointer>
#include <QProcess>
#include <QProgressDialog>
#include <QPromise>
#include <QPushButton>
#include <QRegularExpression>
#include <QSaveFile>
@@ -54,6 +57,7 @@
#include <QToolButton>
#include <QTreeWidget>
#include <QUrl>
#include <QtConcurrentRun>
#include <QtCore>
#ifdef __linux__
@@ -62,6 +66,7 @@
#include <QDBusPendingCallWatcher>
#endif
#include <atomic>
#include <memory>
#include <vector>
@@ -1171,6 +1176,71 @@ void ConfigurationListWidget::remove_save_data() {
}
}
void ConfigurationListWidget::compress_game() {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->currentItem());
if (item == nullptr) {
return;
}
const auto base = item->GetInfo().basedir;
const auto archive = base + QStringLiteral(".zar");
const auto title = tr("Compress to ZAR");
if (QFileInfo::exists(archive)) {
QMessageBox::warning(this, title,
tr("%1 already exists.").arg(QDir::toNativeSeparators(archive)));
return;
}
// Cancel through a flag, not the future, so a pack that already succeeded keeps its result.
auto canceled = std::make_shared<std::atomic_bool>(false);
auto* progress = new QProgressDialog(
tr("Compressing %1...").arg(QDir::toNativeSeparators(base)), tr("Cancel"), 0, 1000, this);
auto* watcher = new QFutureWatcher<bool>(this);
// Stay modal through finalization and fsync, which follow the last progress step.
progress->setAutoReset(false);
connect(progress, &QProgressDialog::canceled, this, [canceled] { *canceled = true; });
connect(watcher, &QFutureWatcher<bool>::progressValueChanged, progress,
&QProgressDialog::setValue);
connect(watcher, &QFutureWatcher<bool>::finished, this,
[this, progress, watcher, canceled, base, archive, title] {
// finished can arrive inside the modal dialog's setValue event processing.
progress->deleteLater();
watcher->deleteLater();
if (!watcher->result()) {
if (!*canceled) {
QMessageBox::warning(
this, title,
tr("Could not compress %1.").arg(QDir::toNativeSeparators(base)));
}
return;
}
if (QMessageBox::Yes ==
QMessageBox::question(this, title,
tr("Created %1.\n\nDelete the original folder %2?")
.arg(QDir::toNativeSeparators(archive),
QDir::toNativeSeparators(base)),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) &&
!QDir(base).removeRecursively()) {
QMessageBox::warning(
this, title, tr("Could not remove %1.").arg(QDir::toNativeSeparators(base)));
}
ScanGameDirectory();
});
progress->open();
watcher->setFuture(QtConcurrent::run(
[directory = GameContent::ToPath(base), path = GameContent::ToPath(archive),
canceled](QPromise<bool>& promise) {
promise.setProgressRange(0, 1000);
// A pack cancelled while queued must not touch a path a later pack may already own.
promise.addResult(
!*canceled &&
Common::PackArchive(directory, path, [&](uint64_t done, uint64_t total) {
promise.setProgressValue(static_cast<int>(done * 1000 / total));
return !*canceled;
}));
}));
}
void ConfigurationListWidget::filter_configurations(const QString& text) {
const auto query = text.trimmed();
const bool has_query = !query.isEmpty();
@@ -1268,6 +1338,8 @@ void ConfigurationListWidget::ShowContextMenu(QTreeWidgetItem* witem) {
}
});
action_addcont->setEnabled(item != nullptr);
QAction* action_compress =
menu.addAction(tr("Compress to ZAR..."), this, SLOT(compress_game()));
QAction* action_remove_save_data =
menu.addAction(style()->standardIcon(QStyle::SP_DialogDiscardButton),
tr("Remove save data..."), this, SLOT(remove_save_data()));
@@ -1291,6 +1363,7 @@ void ConfigurationListWidget::ShowContextMenu(QTreeWidgetItem* witem) {
const auto& base = item->GetInfo().basedir;
action_open_folder->setDisabled(!QDir(base).exists() && !GameContent::IsArchive(base));
action_view_trophies->setDisabled(!has_trophy_data);
action_compress->setDisabled(item->IsRunning() || !QDir(base).exists());
action_remove_save_data->setDisabled(item->IsRunning() || save_data_dirs.isEmpty());
action_edit->setDisabled(item->IsRunning());
action_delete->setDisabled(item->IsRunning() || !item->GetInfo().custom_settings);
@@ -1298,6 +1371,7 @@ void ConfigurationListWidget::ShowContextMenu(QTreeWidgetItem* witem) {
action_run->setDisabled(true);
action_open_folder->setDisabled(true);
action_view_trophies->setDisabled(true);
action_compress->setDisabled(true);
action_remove_save_data->setDisabled(true);
action_edit->setDisabled(true);
action_delete->setDisabled(true);
+47 -18
View File
@@ -178,7 +178,6 @@ struct AudioOut2ContextState {
uint32_t num_grains = 512;
uint64_t last_update = 0;
int object_audio_handle = 0;
std::vector<float> object_mix;
};
struct AudioOut2PortStateEntry {
@@ -212,8 +211,18 @@ struct AudioOut2LatencyState {
uint32_t latency_us = 0;
};
// Per-context copy of what a push hands the backend. The backend can sleep while pacing, so a push
// holds only its context's mutex across it; closing the context's handles waits on that mutex.
struct AudioOut2Submission {
Common::Mutex mutex;
std::array<std::vector<uint8_t>, AudioInternal::OUT_PORTS_MAX> pcm;
std::array<std::array<float, 16>, AudioInternal::OUT_PORTS_MAX> gains;
std::vector<float> object_mix;
};
static Common::Mutex g_audioout2_state_mutex;
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
static std::vector<AudioOut2UserHandle> g_audioout2_users;
static Common::Mutex g_audioout2_speaker_array_mutex;
@@ -249,6 +258,10 @@ static AudioOut2ContextState* audioout2_find_context_locked(AudioOut2ContextHand
return nullptr;
}
static AudioOut2Submission& audioout2_submission(const AudioOut2ContextState& context) {
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
}
static uint32_t audioout2_grain_micros(uint32_t grains) {
const auto sample_count = (grains == 0 ? 512u : grains);
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
@@ -372,11 +385,10 @@ static bool audioout2_context_has_queueable_device(const AudioOut2ContextState&
return false;
}
static void audioout2_mix_object(AudioOut2ContextState& context,
static void audioout2_mix_object(std::vector<float>& mix, uint32_t num_grains,
const AudioOut2PortStateEntry& object) {
auto& mix = context.object_mix;
if (mix.empty()) {
mix.resize(static_cast<size_t>(context.num_grains) * 2, 0.0f);
mix.resize(static_cast<size_t>(num_grains) * 2, 0.0f);
}
// Center ordinary objects. First-order stereo ambisonics uses left/right-facing
@@ -393,7 +405,7 @@ static void audioout2_mix_object(AudioOut2ContextState& context,
}
left *= object.gains[0];
right *= object.gains[0];
for (uint32_t frame = 0; frame < context.num_grains; frame++) {
for (uint32_t frame = 0; frame < num_grains; frame++) {
const float sample =
object.audio_format == AudioInternal::Format::FloatMono
? reinterpret_cast<const float*>(object.pcm_data.data())[frame]
@@ -403,15 +415,16 @@ static void audioout2_mix_object(AudioOut2ContextState& context,
}
}
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
// The backend consumes the buffers synchronously, but can block while pacing SDL's queue.
// Keep both PCM storage and port handles alive until it returns.
Common::LockGuard lock(g_audioout2_state_mutex);
auto* context = audioout2_find_context_locked(ctx);
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2Submission& out,
bool blocking) {
Common::LockGuard submit_lock(out.mutex);
g_audioout2_state_mutex.Lock();
auto* context = audioout2_find_context_locked(ctx);
if (context == nullptr) {
g_audioout2_state_mutex.Unlock();
return;
}
context->object_mix.clear();
out.object_mix.clear();
std::vector<AudioInternal::OutputParam> params;
params.reserve(AudioInternal::OUT_PORTS_MAX);
for (const auto& state: g_audioout2_ports) {
@@ -419,14 +432,18 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool block
continue;
}
if (audioout2_port_type_is_object(state.port_type)) {
audioout2_mix_object(*context, state);
audioout2_mix_object(out.object_mix, context->num_grains, state);
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
params.push_back({state.audio_handle, state.pcm_data.data(), state.gains.data()});
const auto i = params.size();
out.pcm[i] = state.pcm_data;
out.gains[i] = state.gains;
params.push_back({state.audio_handle, out.pcm[i].data(), out.gains[i].data()});
}
}
if (!context->object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
params.push_back({context->object_audio_handle, context->object_mix.data()});
if (!out.object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
params.push_back({context->object_audio_handle, out.object_mix.data()});
}
g_audioout2_state_mutex.Unlock();
if (!params.empty()) {
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
blocking);
@@ -528,7 +545,8 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
size_t audio_handles_num = 0;
g_audioout2_state_mutex.Lock();
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
auto* state = audioout2_find_context_locked(ctx);
if (state != nullptr) {
if (state->object_audio_handle > 0) {
audio_handles[audio_handles_num++] = state->object_audio_handle;
}
@@ -544,6 +562,10 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
}
g_audioout2_state_mutex.Unlock();
if (state != nullptr) {
// Wait for an in-flight push that may still use the handles.
Common::LockGuard wait(audioout2_submission(*state).mutex);
}
for (size_t i = 0; i < audio_handles_num; i++) {
audioout2_close_audio_handle(audio_handles[i]);
}
@@ -595,8 +617,9 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
if (state->queued < state->queue_depth) {
state->queued++;
}
auto& submission = audioout2_submission(*state);
g_audioout2_state_mutex.Unlock();
audioout2_queue_context_audio(ctx, blocking != 0);
audioout2_queue_context_audio(ctx, submission, blocking != 0);
return OK;
}
}
@@ -729,14 +752,20 @@ int KYTY_SYSV_ABI AudioOut2PortDestroy(AudioOut2PortHandle port) {
PRINT_NAME();
LOGF("\t port = 0x%016" PRIx64 "\n", port);
int audio_handle = 0;
int audio_handle = 0;
const AudioOut2ContextState* context = nullptr;
g_audioout2_state_mutex.Lock();
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
audio_handle = state->audio_handle;
context = audioout2_find_context_locked(state->context);
*state = AudioOut2PortStateEntry {};
}
g_audioout2_state_mutex.Unlock();
if (audio_handle > 0 && context != nullptr) {
// Wait for an in-flight push that may still use the handle.
Common::LockGuard wait(audioout2_submission(*context).mutex);
}
audioout2_close_audio_handle(audio_handle);
return OK;
+69
View File
@@ -12,6 +12,7 @@
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <thread>
@@ -143,6 +144,72 @@ void CheckMalformedArchives(const std::filesystem::path &source,
"recover by reading an unaffected compressed block");
}
void WriteHostFile(const std::filesystem::path &path, const void *data, size_t size) {
std::ofstream stream(path, std::ios::binary);
stream.write(static_cast<const char *>(data), static_cast<std::streamsize>(size));
Check(stream.good(), "write pack source file");
}
void CheckPackArchive(const std::filesystem::path &root, const std::vector<uint8_t> &payload) {
const auto source = root / "pack-source";
const auto unicode = Common::PathFromUtf8(ArchiveTests::UnicodeFilename);
std::error_code error;
Check(std::filesystem::create_directories(source / "assets/subdir", error) &&
std::filesystem::create_directories(source / "empty", error),
"create pack source tree");
WriteHostFile(source / "eboot.bin", ArchiveTests::Eboot.data(), ArchiveTests::Eboot.size());
WriteHostFile(source / "assets/subdir/data.bin", payload.data(), payload.size());
WriteHostFile(source / unicode, nullptr, 0);
uint64_t expected = ArchiveTests::Eboot.size() + payload.size();
#if !defined(_WIN32)
std::filesystem::create_directory_symlink("assets", source / "linked", error);
Check(!error, "link a pack source directory");
expected += payload.size();
#endif
const auto archive = root / "packed.zar";
uint64_t done = 0;
uint64_t total = 0;
Check(Common::PackArchive(source, archive,
[&](uint64_t packed, uint64_t size) {
done = packed;
total = size;
return true;
}),
"pack a host directory");
Check(done == expected && total == expected, "report every packed byte");
const auto packed = Common::MakeArchivePath(archive);
const auto check_payload = [&](const std::filesystem::path &member) {
Common::File file(packed / member, Common::File::Mode::Read);
const auto data = file.ReadWholeBuffer();
Check(data.size() == payload.size() &&
std::memcmp(data.data(), payload.data(), payload.size()) == 0,
"round-trip packed file contents");
};
check_payload("assets/subdir/data.bin");
#if !defined(_WIN32)
check_payload("linked/subdir/data.bin");
#endif
const auto empty_file = Common::File::GetInfo(packed / unicode);
Check(empty_file && empty_file->is_file && empty_file->size == 0 &&
Common::File::IsDirectoryExisting(packed / "empty"),
"keep empty files and directories");
const auto failed = root / "failed.zar";
const auto rejected = [&](const std::filesystem::path &directory, bool proceed) {
return !Common::PackArchive(directory, failed, [=](uint64_t, uint64_t) { return proceed; }) &&
!std::filesystem::exists(failed, error);
};
Check(rejected(source, false), "cancelling removes the partial archive");
Check(rejected(root / "missing", true), "reject a missing source directory");
#if defined(__linux__)
// Archive names compare case-insensitively, so such a folder cannot be packed faithfully.
WriteHostFile(source / "EBOOT.BIN", nullptr, 0);
Check(rejected(source, true), "reject names that differ only in case");
#endif
}
#if !defined(_WIN32) || defined(_GLIBCXX_GCC_GTHR_POSIX_H)
void CheckCancellation(const std::filesystem::path &path,
const std::vector<uint8_t> &payload) {
@@ -395,6 +462,8 @@ int main() {
std::memcmp(replaced_data.data(), replacement.data(), replacement.size()) == 0,
"reopening after the final reader closes sees replacement contents");
CheckPackArchive(temporary.Path(), payload);
std::printf("ArchiveFileTests: all cases passed\n");
return 0;
}
+71 -1
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdint>
@@ -32,6 +33,8 @@ size_t g_capture_bytes = 0;
int g_next_device = 1;
int g_open_waiters = 0;
bool g_block_opens = false;
bool g_block_outputs = false;
std::vector<int> g_blocked_output;
void Check(bool value, const char* text) {
if (!value) {
@@ -190,6 +193,22 @@ void ReleaseDeviceOpens() {
g_device_cv.notify_all();
}
void BlockOutputs() {
std::lock_guard lock(g_device_mutex);
g_block_outputs = true;
}
void WaitForBlockedOutput() {
std::unique_lock lock(g_device_mutex);
g_device_cv.wait(lock, []() { return !g_blocked_output.empty(); });
}
void ReleaseOutputs() {
std::lock_guard lock(g_device_mutex);
g_block_outputs = false;
g_device_cv.notify_all();
}
int LiveDeviceCount() {
std::lock_guard lock(g_device_mutex);
return static_cast<int>(g_live_devices.size());
@@ -605,6 +624,46 @@ void TestObjectBusRouting() {
Check(LiveDeviceCount() == 0, "object bus device leaked");
}
void TestBlockedPushOnlyHoldsItsContext() {
const auto blocked = CreateContext(4, 1);
const auto other = CreateContext();
const auto blocked_port = CreatePort(blocked, MakeParam());
const auto other_port = CreatePort(other, MakeParam());
auto object_param = MakeParam(0x100);
object_param.port_type = 0x100;
std::vector<float> pcm(512 * 2, 0.25f);
SetPcm(blocked_port, pcm.data());
SetPcm(CreatePort(blocked, object_param), pcm.data());
CaptureOutputPcm(pcm.size() * sizeof(float));
BlockOutputs();
std::thread pusher([&]() { Check(AudioOut2::AudioOut2ContextPush(blocked, 1) == OK, "blocked push failed"); });
WaitForBlockedOutput();
std::fill(pcm.begin(), pcm.end(), 0.5f);
SetPcm(blocked_port, pcm.data());
SetAttribute(blocked_port, 1, std::array {0.5f, 0.5f});
SetPcm(other_port, pcm.data());
Check(AudioOut2::AudioOut2ContextPush(other, 1) == OK, "push stalled behind another context");
// Give each destroyer time to close a device of the blocked output; the backend fails if it does.
std::thread port_destroyer([&]() { AudioOut2::AudioOut2PortDestroy(blocked_port); });
std::this_thread::sleep_for(std::chrono::milliseconds(20));
std::thread context_destroyer([&]() { AudioOut2::AudioOut2ContextDestroy(blocked); });
std::this_thread::sleep_for(std::chrono::milliseconds(20));
ReleaseOutputs();
pusher.join();
port_destroyer.join();
context_destroyer.join();
const auto output = OutputPcm();
Check(output.size() == 3, "pushes did not reach the backend once each");
CheckSamples(output[0], {0.5f}, "other context missed its PCM");
CheckSamples(output[1], {0.25f}, "PCM changed while its push was in flight");
Check(g_output_gains[0] == std::array {1.0f, 1.0f}, "gains changed while their push was in flight");
Check(LiveDeviceCount() == 1, "destroyed port or context kept its device");
CaptureOutputPcm(0);
AudioOut2::AudioOut2ContextDestroy(other);
}
} // namespace
namespace Libs::Audio::AudioInternal {
@@ -625,6 +684,8 @@ int AudioOutOpen(int type, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /
void AudioOutClose(int handle) {
std::lock_guard lock(g_device_mutex);
const auto it = std::find(g_live_devices.begin(), g_live_devices.end(), handle);
Check(std::find(g_blocked_output.begin(), g_blocked_output.end(), handle) == g_blocked_output.end(),
"device closed during its output");
if (it != g_live_devices.end()) {
g_live_devices.erase(it);
}
@@ -642,8 +703,16 @@ bool AudioOutHasDevice(int handle) {
}
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
std::lock_guard lock(g_device_mutex);
std::unique_lock lock(g_device_mutex);
g_output_blocking.push_back(blocking);
if (g_block_outputs && g_blocked_output.empty()) {
for (uint32_t i = 0; i < num; i++) {
g_blocked_output.push_back(params[i].handle);
}
g_device_cv.notify_all();
g_device_cv.wait(lock, []() { return !g_block_outputs; });
g_blocked_output.clear();
}
g_output_gains.clear();
if (g_capture_bytes != 0) {
for (uint32_t i = 0; i < num; i++) {
@@ -683,6 +752,7 @@ int main() {
TestPortGainAndValidation();
TestObjectGainAndAmbisonics();
TestObjectBusRouting();
TestBlockedPushOnlyHoldsItsContext();
Check(AudioOut2::AudioOut2UserDestroy(g_user_handle) == OK, "test user destroy failed");
std::printf("AudioOut2PortTests: all cases passed\n");
return 0;
+61 -10
View File
@@ -33076,7 +33076,7 @@ std::vector<TestCase> BufferLoadFormatD16XCases() {
return cases;
}
std::vector<TestCase> BufferLoadFormatD16XyzwCases() {
std::vector<TestCase> BufferLoadFormatD16VectorCases() {
using F = Prospero::BufferFormat;
using O = ShaderOpcode;
struct LoadCase {
@@ -33089,6 +33089,7 @@ std::vector<TestCase> BufferLoadFormatD16XyzwCases() {
u32 index = 0;
bool active = true;
bool overlap = false;
u32 opcode = 0x83u;
};
constexpr LoadCase inputs[] = {
{"BufferLoadFormatD16XyzwFloat32", F::k32_32_32_32Float,
@@ -33118,7 +33119,26 @@ std::vector<TestCase> BufferLoadFormatD16XyzwCases() {
{"BufferLoadFormatD16XyzwOverlappingAddress", F::k32_32_32_32Float,
{0x3f800000u, 0x3f000000u, 0u, 0xc0000000u},
{0x38003c00u, 0xc0000000u}, DstSel(4, 5, 6, 7), 16, 0, true, true},
{"BufferLoadFormatD16XyFloat32", F::k32_32_32_32Float,
{0x3f800000u, 0x3f000000u, 0u, 0xc0000000u},
{0x38003c00u, 0xbeef5678u}, DstSel(4, 5, 6, 7), 16, 0, true, false, 0x81u},
{"BufferLoadFormatD16XyUintSaturates", F::k32_32UInt,
{0x10000u, 7u}, {0x0007ffffu, 0xbeef5678u}, DstSel(4, 5, 6, 7), 8, 0, true, false, 0x81u},
{"BufferLoadFormatD16XyzFloat32", F::k32_32_32_32Float,
{0x3f800000u, 0x3f000000u, 0xc0000000u, 0x3e800000u},
{0x38003c00u, 0xbeefc000u}, DstSel(4, 5, 6, 7), 16, 0, true, false, 0x82u},
{"BufferLoadFormatD16XyzSintSaturates", F::k32_32_32_32SInt,
{0xffff7fffu, 7u, 0x8000u, 0xffff8000u},
{0x00078000u, 0xbeef7fffu}, DstSel(4, 5, 6, 7), 16, 0, true, false, 0x82u},
{"BufferLoadFormatD16XyzOobOne", F::k32_32_32_32Float,
{}, {0x3c00u, 0xbeef3c00u}, DstSel(1, 4, 1, 4), 16, 1, true, false, 0x82u},
{"BufferLoadFormatD16XyzZeroStrideFloatOne", F::k32_32_32_32Float,
{}, {0x3c003c00u, 0xbeef0000u}, DstSel(1, 1, 4, 1), 0, 0, true, false, 0x82u},
{"BufferLoadFormatD16XyzInactive", F::k32_32_32_32Float,
{}, {0xabcd1234u, 0xbeef5678u}, DstSel(4, 5, 6, 7), 16, 0, false, false, 0x82u},
};
constexpr O formats[] = {O::BUFFER_LOAD_FORMAT_D16_XY, O::BUFFER_LOAD_FORMAT_D16_XYZ,
O::BUFFER_LOAD_FORMAT_D16_XYZW};
std::vector<TestCase> cases;
for (const auto &input : inputs) {
TestCase test;
@@ -33128,8 +33148,8 @@ std::vector<TestCase> BufferLoadFormatD16XyzwCases() {
AppendVMovLiteral(&test.code, 18, 0xcafef00du);
AppendVMovU32(&test.code, 12, input.index);
if (!input.active) test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
// Captured b21e373811f28e31 pc 0x1914: e20c2000 8001100c.
test.code.push_back(EncodeMubuf0(0x83u, 0, true, false));
// Captured XYZW b21e373811f28e31 pc 0x1914: e20c2000 8001100c.
test.code.push_back(EncodeMubuf0(input.opcode, 0, true, false));
test.code.push_back(EncodeMubuf1(16, 1, input.overlap ? 16 : 12));
if (!input.active) {
test.code.push_back(EncodeSMovB32(126, InlineU32(1)));
@@ -33151,7 +33171,7 @@ std::vector<TestCase> BufferLoadFormatD16XyzwCases() {
if (input.stride != 0 && input.active) {
test.storage_buffer_ranges = {{0, input.stride}, {0, 32}};
}
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_D16_XYZW,
test.opcodes = {O::V_MOV_B32, formats[input.opcode - 0x81u],
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
cases.push_back(std::move(test));
}
@@ -33195,12 +33215,23 @@ std::vector<TestCase> BufferStoreFormatD16Cases() {
{0xff80ff7fu, 0x0080007fu}, {0x7f7f8080u}, 1, 0x87u},
{"BufferStoreFormatD16XyzwUnorm8", F::k8_8_8_8UNorm,
{0x3800c000u, 0x40003c00u}, {0xffff8000u}, 1, 0x87u},
{"BufferStoreFormatD16XyFloat32", F::k32_32Float,
{0x3800c000u}, {0xc0000000u, 0x3f000000u}, 2, 0x85u},
{"BufferStoreFormatD16XyUint16", F::k16_16UInt, {0xfffe0001u}, {0xfffe0001u}, 1, 0x85u},
{"BufferStoreFormatD16XyzFloat32", F::k32_32_32Float,
{0xc0003c00u, 0xdead3800u}, {0x3f800000u, 0xc0000000u, 0x3f000000u}, 3, 0x86u},
{"BufferStoreFormatD16XyzZeroFillsW", F::k16_16_16_16Float,
{0xc0003c00u, 0xdead3800u}, {0xc0003c00u, 0x00003800u}, 2, 0x86u},
{"BufferStoreFormatD16XyzUint8", F::k8_8_8_8UInt,
{0x00020001u, 0xdead0003u}, {0x00030201u}, 1, 0x86u},
};
constexpr O formats[] = {O::BUFFER_STORE_FORMAT_D16_X, O::BUFFER_STORE_FORMAT_D16_XY,
O::BUFFER_STORE_FORMAT_D16_XYZ, O::BUFFER_STORE_FORMAT_D16_XYZW};
std::vector<TestCase> cases;
for (const auto &input : inputs)
for (const u32 wave : {32u, 64u})
for (u32 mask = 0; mask < (input.opcode == 0x27u ? 2u : 1u); ++mask) {
const bool wide = input.opcode == 0x87u;
const bool wide = input.opcode >= 0x85u;
const bool high = input.opcode == 0x27u;
const bool multi_lane = wide || high;
const bool masked = wide || mask != 0;
@@ -33253,9 +33284,9 @@ std::vector<TestCase> BufferStoreFormatD16Cases() {
test.user_data = MakeStructuredStorageBufferData(stride, multi_lane ? wave : 3u,
false, BufferFormat(input.format));
test.has_user_data = true;
test.opcodes = {O::V_MOV_B32, high ? O::BUFFER_STORE_FORMAT_D16_HI_X :
wide ? O::BUFFER_STORE_FORMAT_D16_XYZW :
O::BUFFER_STORE_FORMAT_D16_X, O::S_ENDPGM};
test.opcodes = {O::V_MOV_B32,
high ? O::BUFFER_STORE_FORMAT_D16_HI_X : formats[input.opcode & 3u],
O::S_ENDPGM};
if (high) test.decoded_counts = {{"BUFFER_STORE_FORMAT_D16_HI_X", 3u}};
cases.push_back(std::move(test));
}
@@ -43310,7 +43341,7 @@ std::vector<TestCase> MakeCases() {
for (auto &test : BufferLoadFormatD16XCases()) {
cases.push_back(std::move(test));
}
for (auto &test : BufferLoadFormatD16XyzwCases()) {
for (auto &test : BufferLoadFormatD16VectorCases()) {
cases.push_back(std::move(test));
}
for (auto &test : BufferStoreFormatD16Cases()) {
@@ -44928,6 +44959,26 @@ void CheckImageTransitionState(RenderContext &renderer) {
ImageTestAccess::CopyRows(copy_capacity + 4, 1, copy_capacity) == 0,
"buffered image copy does not split at the fixed scratch capacity");
// RADV rejects 3D 128-bit BC images with block-texel views plus extended usage.
auto volume_info = MakeInfo(vk::Format::eBc7UnormBlock, 1, 1);
volume_info.guest_format = Prospero::BufferFormat::kBc7UNorm;
volume_info.type = Prospero::ImageType::kColor3D;
volume_info.extent.depth = 4;
volume_info.bytes_per_block = 16;
for (const bool supported : {false, context.supports_block_texel_view}) {
context.supports_block_texel_view = supported;
Image volume(context, scheduler, volume_info);
const auto flags = volume.backing.flags;
Require(name, "block volume create flags",
(flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
static_cast<bool>(flags & vk::ImageCreateFlagBits::eBlockTexelViewCompatible) ==
supported &&
static_cast<bool>(flags & vk::ImageCreateFlagBits::eExtendedUsage) ==
static_cast<bool>(volume.backing.usage &
vk::ImageUsageFlagBits::eStorage),
"block volume requested extended usage without storage");
}
Image image(context, scheduler, MakeInfo(vk::Format::eR8Unorm, 2, 3));
auto barriers =
image.GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal,
@@ -49268,7 +49319,7 @@ int main(int argc, char **argv) {
for (const auto &test : BufferLoadFormatD16XCases()) {
RunCase(&vulkan, test);
}
for (const auto &test : BufferLoadFormatD16XyzwCases()) {
for (const auto &test : BufferLoadFormatD16VectorCases()) {
RunCase(&vulkan, test);
}
for (const auto &test : BufferStoreFormatD16Cases()) {
+81
View File
@@ -6832,6 +6832,50 @@ void TestCustomVintrpMovTranslation() {
CheckSpirvBinaryValidates(mixed_linear_result.spirv);
}
void TestVop3F16InterpolationTranslation() {
// v_interp_p1ll_f16 v21, v0, attr0.w high; v_interp_p2_f16 v14, v1, attr0.w, v21 high
// v_interp_p1ll_f16 v15, v0, attr0.z; v_interp_p2_f16 v14, v1, attr0.z, v15 (op_sel[3])
const uint32_t shader[] = {
EncodeVop3Word0(0x342, 21), EncodeVop3Word1(0x1c0, 256 + 0, 0),
EncodeVop3Word0(0x35a, 14), EncodeVop3Word1(0x1c0, 256 + 1, 256 + 21),
EncodeVop3Word0(0x342, 15), EncodeVop3Word1(0x080, 256 + 0, 0),
EncodeVop3Word0(0x35a, 14, 8), EncodeVop3Word1(0x080, 256 + 1, 256 + 15),
EncodeExp0(0x00, 0x1), EncodeExp1(14, 0, 0, 0),
0xbf810000u,
};
ShaderPixelInputInfo ps_info{};
ps_info.input_num = 1;
SetIdentityInterpolatorSettings(&ps_info);
auto options = MakeCompileOptions(ShaderType::Pixel);
options.input_info.pixel = &ps_info;
options.dump_ir = true;
auto result = RecompileForTest(shader, options);
Check(result.ir_dump.find("GetAttributeF16 0x00000000, 0x00000003, 0x00000001") !=
std::string::npos,
"v_interp_p2_f16 did not read the high f16 of attr0.w");
Check(result.ir_dump.find("GetAttributeF16 0x00000000, 0x00000002, 0x00000000") !=
std::string::npos,
"v_interp_p2_f16 did not read the low f16 of attr0.z");
Check(result.decoded_dump.find("v14.sdwa(sel=5") != std::string::npos,
"v_interp_p2_f16 op_sel[3] did not write the high half of v14");
Check(SpirvHasDecorationValueWithDecoration(result.spirv, 30u, 0u, 5285u),
"f16 interpolation input was not per-vertex");
Check(SpirvExtInstCount(result.spirv, 62u) == 6u,
"f16 interpolation did not unpack each of the three vertices per read");
Check(SpirvInstructionOpcodeCount(result.spirv, 133u) == 6u,
"f16 interpolation did not blend three barycentric weights per read");
CheckSpirvBinaryValidates(result.spirv);
ps_info.interpolator_settings[0] = 0x00000400u;
auto flat_result = RecompileForTest(shader, options);
Check(SpirvExtInstCount(flat_result.spirv, 62u) == 2u &&
!SpirvContainsCapability(flat_result.spirv, 5284u) &&
SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u),
"flat f16 interpolation did not unpack only the flat provoking value");
CheckSpirvBinaryValidates(flat_result.spirv);
}
void TestPerspectiveCentroidInputs() {
constexpr std::array cases{std::array{4u, UINT32_MAX, 0u},
std::array{5u, UINT32_MAX, 2u},
@@ -16024,6 +16068,40 @@ void TestCapturedBufferStoreFormatD16HiXDecode() {
"captured BUFFER_STORE_FORMAT_D16_HI_X lost its high half, index, or coherence flags");
}
void TestCapturedBufferLoadD16XyzwDecode() {
namespace Decoder = ShaderRecompiler::Decoder;
const uint32_t code[] = {0xe20c2000u, 0x8001100cu};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW &&
inst.word_count == 2 && inst.data_dwords == 2 && inst.data_bits == 16 &&
inst.data_components == 4 && inst.formatted && !inst.typed,
"captured D16 XYZW load lost its four components in two packed VGPRs");
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 16 &&
inst.dst.sdwa_sel == 6 &&
inst.src0.kind == Decoder::OperandKind::Vgpr && inst.src0.reg == 12 &&
inst.src1.kind == Decoder::OperandKind::Sgpr && inst.src1.reg == 4 &&
inst.idxen && !inst.offen,
"captured D16 XYZW load lost its full destination VGPRs or index");
}
void TestCapturedBufferLoadD16XyDecode() {
namespace Decoder = ShaderRecompiler::Decoder;
const uint32_t code[] = {0xe2042000u, 0x80090600u};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY &&
inst.word_count == 2 && inst.data_dwords == 1 && inst.data_bits == 16 &&
inst.data_components == 2 && inst.formatted && !inst.typed,
"captured D16 XY load lost its two components in one packed VGPR");
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 6 &&
inst.dst.sdwa_sel == 6 &&
inst.src0.kind == Decoder::OperandKind::Vgpr && inst.src0.reg == 0 &&
inst.src1.kind == Decoder::OperandKind::Sgpr && inst.src1.reg == 36 &&
inst.idxen && !inst.offen,
"captured D16 XY load lost its destination VGPR or index");
}
void TestCapturedBufferStoreD16XyzwDecode() {
namespace Decoder = ShaderRecompiler::Decoder;
const uint32_t code[] = {0xe21c6000u, 0x8002000eu};
@@ -16828,6 +16906,7 @@ int main() {
TestNewShaderRecompilerNativeBindingPlan();
TestNewShaderRecompilerStageInputInfo();
TestCustomVintrpMovTranslation();
TestVop3F16InterpolationTranslation();
TestPerspectiveCentroidInputs();
TestPerspectiveSampleInputs();
TestGraphicsCreateInterpolantMapping();
@@ -16838,6 +16917,8 @@ int main() {
TestCapturedBufferLoadUbyteD16Decode();
TestCapturedBufferStoreShortD16HiDecode();
TestCapturedBufferStoreFormatD16HiXDecode();
TestCapturedBufferLoadD16XyDecode();
TestCapturedBufferLoadD16XyzwDecode();
TestCapturedBufferStoreD16XyzwDecode();
TestNewShaderRecompilerUnsupportedMemoryDecode();