Compare commits

..
Author SHA1 Message Date
nmzik 5e837a9bea shader: share formatted indirect buffer load emission 2026-10-29 05:52:04 +01:00
nmzikandFrenzied a0b99a8c21 ngs2: accept larger custom voice state buffers
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
nmzikandFrenzied 8cfe1c4d34 ngs2: apply custom channel matrices before mastering output
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
Çağatay Güçlü f71da54b9b shader: support fractional-even tessellation spacing 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 63207739bc shader: initialize local vertex count from active lanes 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 143f640706 shader: preserve counterclockwise tessellation output 2026-10-29 05:52:04 +01:00
nmzik 54b937c85f shader: support high products of 24-bit integers 2026-10-28 22:49:21 +01:00
nmzik 22610cb93e shader: support unsigned 16-bit multiply-add 2026-10-28 22:41:29 +01:00
nmzik 0386f33bf3 shader: support four 16-bit integer ternary operations 2026-10-28 22:37:24 +01:00
nmzik 138d5b86c4 shader: support S_QUADMASK_B32 2026-10-28 22:31:16 +01:00
nmzik 963aaeb3b6 gpu: preserve low-alpha 2/10/10/10 render-target storage 2026-10-28 22:29:50 +01:00
Çağatay Güçlü 77458260f1 shader: reflect masked hull control-point addresses 2026-10-28 22:19:09 +01:00
nmzik 41de2fd64e shader: support five VOP3 float conversion forms 2026-10-28 22:19:02 +01:00
Çağatay Güçlü 128c938514 shader: support quad tessellation domains and factor routing 2026-10-28 22:09:39 +01:00
Chai T a8e3c29b2e shader: implement V_ASHRREV_I64 (VOP3 0x301) 2026-10-28 21:51:28 +01:00
Devran Cosmo Uenal c528324d27 audio: let the AJM Opus decoder write part of a packet (#1914)
ajm: buffer and resample Opus output
2026-10-28 21:31:16 +01:00
Mr. Big BritchesandClaude Opus 5.5 c67352d5d6 ci: build and run the NGS2 sampler tests (#1957)
ngs2_sampler_tests is registered with ctest but no job built it or listed
it in the test filter, so the NGS2 unit tests never ran in CI. Each
platform now builds the target and includes ngs2_sampler in its ctest
filter.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:58:06 +01:00
Nawid3333 6c01401862 graphics: accept video-out buffers with 128-byte independent DCC blocks (Gran Turismo 7) (#1945)
graphics: accept video-out buffers with 128-byte independent DCC blocks
2026-10-28 04:44:09 +01:00
nmzik a833e1b735 graphics: fix vertex and instance IDs for unpatched indirect draws 2026-10-28 04:33:03 +01:00
36 changed files with 1981 additions and 436 deletions

No files matched your search

+6 -6
View File
@@ -113,7 +113,7 @@ jobs:
shell: cmd shell: cmd
run: | run: |
cmake --build _Build/windows --target launcher ^ cmake --build _Build/windows --target launcher ^
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^ audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
shader_recompiler_compute_tests ^ shader_recompiler_compute_tests ^
resource_tracking_tests scalar_provenance_tests ^ resource_tracking_tests scalar_provenance_tests ^
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
@@ -121,7 +121,7 @@ jobs:
- name: Test - name: Test
shell: cmd shell: cmd
run: | run: |
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$" ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
- name: Install - name: Install
shell: cmd shell: cmd
@@ -202,7 +202,7 @@ jobs:
shell: bash shell: bash
run: | run: |
cmake --build _Build/macos \ cmake --build _Build/macos \
--target launcher audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \ --target launcher audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \ memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
resource_tracking_tests scalar_provenance_tests \ resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \ resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
@@ -212,7 +212,7 @@ jobs:
shell: bash shell: bash
run: | run: |
ctest --test-dir _Build/macos --output-on-failure \ ctest --test-dir _Build/macos --output-on-failure \
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$' -R '^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
- name: Install - name: Install
shell: bash shell: bash
@@ -388,7 +388,7 @@ jobs:
run: | run: |
cmake --build _Build/linux \ cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \ --target launcher page_manager_tests memory_tracker_tests \
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \ audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
resource_tracking_tests scalar_provenance_tests \ resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \ resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
--parallel --parallel
@@ -397,7 +397,7 @@ jobs:
shell: bash shell: bash
run: | run: |
ctest --test-dir _Build/linux --output-on-failure \ ctest --test-dir _Build/linux --output-on-failure \
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$' -R '^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
- name: Install - name: Install
shell: bash shell: bash
+19
View File
@@ -522,6 +522,13 @@ add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9) target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers}) target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
add_executable(ajm_resample_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AjmResampleTests.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(ajm_resample_tests common fmt::fmt FFmpeg::ffmpeg LibAtrac9 opus)
target_include_directories(ajm_resample_tests PRIVATE ${inc_headers})
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp" "${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
) )
@@ -668,6 +675,7 @@ if(BUILD_TESTING)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>) add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>) add_test(NAME audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>)
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>) add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
add_test(NAME ajm_resample COMMAND $<TARGET_FILE:ajm_resample_tests>)
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>) add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>) add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>) add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
@@ -684,6 +692,16 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
add_test(NAME shader_recompiler_float_min_max add_test(NAME shader_recompiler_float_min_max
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
add_test(NAME shader_recompiler_vop3_conversions
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --vop3-conversions-only)
add_test(NAME shader_recompiler_quadmask
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --quadmask-only)
add_test(NAME shader_recompiler_integer16_ternary
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-only)
add_test(NAME shader_recompiler_mad_u16
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mad-u16-only)
add_test(NAME shader_recompiler_mul_hi24
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mul-hi24-only)
add_test(NAME shader_recompiler_alignbyte add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME graphics_pipeline_rasterization add_test(NAME graphics_pipeline_rasterization
@@ -762,6 +780,7 @@ if(BUILD_TESTING)
audio_out2_port_tests audio_out2_port_tests
audio_out_timing_tests audio_out_timing_tests
ngs2_sampler_tests ngs2_sampler_tests
ajm_resample_tests
pad_haptics_tests pad_haptics_tests
controller_settings_tests controller_settings_tests
host_input_tests host_input_tests
+1
View File
@@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, {BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false}, {BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
{BufferFormat::k2_10_10_10UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
+2 -3
View File
@@ -856,9 +856,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
// NUM_INSTANCES keeps the last draw's instance count. // NUM_INSTANCES keeps the last draw's instance count.
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes + m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
offsetof(vk::DrawIndirectCommand, instanceCount); offsetof(vk::DrawIndirectCommand, instanceCount);
// The GPU reads the records if the shaders allow; the VS prolog loads the patched SGPRs from // The GPU reads the records if the shaders allow; the VS prolog removes Vulkan's bases
// Vulkan's bases. With no SGPR patched, Vulkan applies the offsets, which matches while they // from the index VGPRs and copies them only to the user SGPRs the packet patches.
// are zero.
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes}; DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) && if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(), (indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
@@ -17,9 +17,15 @@
namespace Libs::Graphics { namespace Libs::Graphics {
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11 }; enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11, Rotate2_10_10_10 };
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported }; enum class VideoOutCompression : uint8_t {
Uncompressed,
Dcc256_256_0,
Dcc256_64_64,
Dcc128_128_128,
Unsupported
};
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask }; enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask };
@@ -99,6 +105,8 @@ struct ImageInfo {
switch (guest_format) { switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float: case Prospero::BufferFormat::k10_11_11Float:
return ColorTransform::Reverse10_11_11; return ColorTransform::Reverse10_11_11;
case Prospero::BufferFormat::k2_10_10_10UNorm:
return ColorTransform::Rotate2_10_10_10;
default: return ColorTransform::None; default: return ColorTransform::None;
} }
} }
@@ -287,8 +295,9 @@ inline bool ImageInfo::IsDepth() const noexcept {
[[nodiscard]] inline VideoOutCompression [[nodiscard]] inline VideoOutCompression
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control, ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
uint64_t dcc_clear_color) noexcept { uint64_t dcc_clear_color) noexcept {
constexpr uint32_t DCC_256_256_0 = 0x00000048u; constexpr uint32_t DCC_256_256_0 = 0x00000048u;
constexpr uint32_t DCC_256_64_64 = 0x00000208u; constexpr uint32_t DCC_256_64_64 = 0x00000208u;
constexpr uint32_t DCC_128_128_128 = 0x00100024u;
if (!compressed) { if (!compressed) {
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0 return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
? VideoOutCompression::Uncompressed ? VideoOutCompression::Uncompressed
@@ -300,6 +309,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
switch (dcc_control) { switch (dcc_control) {
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0; case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64; case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
case DCC_128_128_128: return VideoOutCompression::Dcc128_128_128;
default: return VideoOutCompression::Unsupported; default: return VideoOutCompression::Unsupported;
} }
} }
@@ -32,8 +32,12 @@ struct HostFormatInfo {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format, HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) { Prospero::ChannelOrder order) {
if (guest_format == Prospero::BufferFormat::k10_11_11Float) { switch (guest_format) {
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra}; case Prospero::BufferFormat::k10_11_11Float:
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k2_10_10_10UNorm:
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingAbgr};
default: break;
} }
if (order == Prospero::ChannelOrder::kAlt) { if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) { switch (guest_format) {
@@ -104,7 +104,8 @@ TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity,
block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 || block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 ||
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0); info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) || EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) ||
(transform == ColorTransform::Reverse10_11_11 && block.bytes_per_element != 4u)); ((transform == ColorTransform::Reverse10_11_11 ||
transform == ColorTransform::Rotate2_10_10_10) && block.bytes_per_element != 4u));
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) { const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true); return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
}; };
@@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
uint32_t element_bytes = 0; uint32_t element_bytes = 0;
switch (transform) { switch (transform) {
case ColorTransform::SwapBgra16: element_bytes = 8; break; case ColorTransform::SwapBgra16: element_bytes = 8; break;
case ColorTransform::Reverse10_11_11: element_bytes = 4; break; case ColorTransform::Reverse10_11_11:
case ColorTransform::Rotate2_10_10_10: element_bytes = 4; break;
default: EXIT("invalid color transform\n"); default: EXIT("invalid color transform\n");
} }
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 || EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
@@ -689,6 +689,7 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh; const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
if (!tess_active && !mesh_active) { if (!tess_active && !mesh_active) {
vertex_info[0].indirect_offsets = state.indirect_offsets; vertex_info[0].indirect_offsets = state.indirect_offsets;
vertex_info[0].indirect_draw = state.indirect_draw;
} }
if (object_primitive_id) { if (object_primitive_id) {
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition. // CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
@@ -139,6 +139,7 @@ struct DrawRenderState {
ShaderPixelInputInfo ps_input_info; ShaderPixelInputInfo ps_input_info;
// Input: the user-data slots a GPU-read indirect draw patches. // Input: the user-data slots a GPU-read indirect draw patches.
ShaderIndirectOffsets indirect_offsets; ShaderIndirectOffsets indirect_offsets;
bool indirect_draw = false;
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const { [[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u}; return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
+19 -7
View File
@@ -52,7 +52,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
const ShaderVertexInputInfo& vs_input_info) { const ShaderVertexInputInfo& vs_input_info) {
auto vertex_offset = static_cast<int32_t>(index_offset); auto vertex_offset = static_cast<int32_t>(index_offset);
uint32_t instance_offset = 0; uint32_t instance_offset = 0;
if (!vs_input_info.fetch_embedded) { if (!vs_input_info.has_dynamic_vertex_fetch) {
return {vertex_offset, instance_offset}; return {vertex_offset, instance_offset};
} }
@@ -1251,16 +1251,26 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
} }
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh // Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry // and quad-list draws for counts, tessellation draws for the index/SGPR prolog. Indirect records
// their own offsets. // carry their own offsets.
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg, static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
const ShaderVertexInputInfo& vs, const ShaderVertexInputInfo& vs,
DrawOffsetSource source, DrawOffsetSource source,
int32_t first_vertex, int32_t first_vertex,
uint32_t first_instance, uint32_t first_instance,
const DrawIndirectArgs& indirect) { const DrawIndirectArgs& indirect) {
if ((indirect.address != 0 || source == DrawOffsetSource::IndirectArgs) &&
ucfg.GetIndexOffset() != 0) {
static std::once_flag warning_once;
std::call_once(warning_once, [&] {
Log::WriteToConsoleAndLog(fmt::format(
"GPU: indirect draw ignores GE_INDX_OFFSET=0x{:08x}; geometry may be incorrect.\n",
ucfg.GetIndexOffset()));
});
}
if (indirect.address != 0) { if (indirect.address != 0) {
if (vs.stage.module->info.stage == ShaderType::Mesh || if (!vs.indirect_draw ||
vs.stage.module->info.stage == ShaderType::Mesh ||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy || ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
vs.indirect_offsets != indirect.offsets) { vs.indirect_offsets != indirect.offsets) {
return std::nullopt; return std::nullopt;
@@ -1269,7 +1279,7 @@ static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig&
} }
if (source == DrawOffsetSource::IndirectArgs) { if (source == DrawOffsetSource::IndirectArgs) {
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets. // Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {}; return vs.has_dynamic_vertex_fetch ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
} }
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs); const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset}; return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
@@ -1326,7 +1336,8 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
hw_check(buffer); hw_check(buffer);
DrawRenderState state {.indirect_offsets = args.indirect.offsets}; DrawRenderState state {.indirect_offsets = args.indirect.offsets,
.indirect_draw = args.indirect.address != 0};
if (!GetDrawTopology(ucfg, state.topology)) { if (!GetDrawTopology(ucfg, state.topology)) {
return true; return true;
} }
@@ -1431,7 +1442,8 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto, const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
args.vertex_count, args.instance_count, args.first_instance}; args.vertex_count, args.instance_count, args.first_instance};
DrawRenderState state {.indirect_offsets = args.indirect.offsets}; DrawRenderState state {.indirect_offsets = args.indirect.offsets,
.indirect_draw = args.indirect.address != 0};
if (!GetDrawTopology(ucfg, state.topology)) { if (!GetDrawTopology(ucfg, state.topology)) {
ResetBindings(); ResetBindings();
return true; return true;
@@ -1,4 +1,4 @@
// ColorTransform values match imageInfo.h. Preserve the floating-point bit fields exactly. // ColorTransform values match imageInfo.h. Preserve the component bit fields exactly.
uvec2 transform_color(uvec2 value, uint transform, bool to_host) { uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
switch (transform) { switch (transform) {
case 1u: case 1u:
@@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
} }
break; break;
} }
case 3u:
if (to_host) {
value.x = (value.x >> 2u) | (value.x << 30u);
} else {
value.x = (value.x << 2u) | (value.x >> 30u);
}
break;
} }
return value; return value;
} }
@@ -814,7 +814,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
const auto& cfg = *selected_cfg; const auto& cfg = *selected_cfg;
Frontend::EmbeddedFetchPlan embedded_fetch; Frontend::EmbeddedFetchPlan embedded_fetch;
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) && if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
options.input_info.vertex->fetch_embedded) { options.input_info.vertex->has_dynamic_vertex_fetch) {
embedded_fetch = DetectEmbeddedVertexFetch( embedded_fetch = DetectEmbeddedVertexFetch(
decoded, *options.input_info.vertex, options.user_data_base, decoded, *options.input_info.vertex, options.user_data_base,
static_cast<uint32_t>(options.user_data.size()), options.wave_size); static_cast<uint32_t>(options.user_data.size()), options.wave_size);
@@ -156,11 +156,14 @@ void ReflectTessellationLayout(const Decoder::Program& program, bool local,
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u); if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
} }
break; break;
case Opcode::V_AND_B32: case Opcode::V_AND_B32: {
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) || const auto mask = lhs.kind == Kind::PackedControlPoint ? rhs
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu))) : rhs.kind == Kind::PackedControlPoint ? lhs
value = constant(0u); : Address {};
if (mask == constant(0xffu)) value = constant(0u);
if (mask == constant(0x1f00u)) value = {Kind::Affine, 256u, 0u};
break; break;
}
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break; case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break;
case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); break; case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); break;
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break; case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
@@ -118,7 +118,7 @@ struct EmitterState {
uint32_t bda_pointer_function = 0; uint32_t bda_pointer_function = 0;
uint32_t indirect_format_function = 0; uint32_t indirect_format_function = 0;
uint32_t indirect_format_geometry_function = 0; uint32_t indirect_format_geometry_function = 0;
std::map<std::array<uint32_t, 3>, uint32_t> indirect_buffer_load_functions; std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions;
uint32_t bvh_intersect_function = 0; uint32_t bvh_intersect_function = 0;
uint32_t gds_variable = 0; uint32_t gds_variable = 0;
uint32_t gds_length = 0; uint32_t gds_length = 0;
@@ -540,7 +540,6 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
void EmitProgram(EmitterState& state); void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state); void DefineGetBdaPointer(EmitterState& state);
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
void DefineIndirectBufferLoads(ValueEmitContext& ctx); void DefineIndirectBufferLoads(ValueEmitContext& ctx);
void DefineBvhIntersect(EmitterState& state); void DefineBvhIntersect(EmitterState& state);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address); uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
@@ -1026,53 +1026,50 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
return ConstructU32Composite(state, components, values); return ConstructU32Composite(state, components, values);
} }
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) { uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IndirectBufferAccess& buffer) {
auto& state = ctx.state; auto& state = ctx.state;
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() { const auto geometry = state.builder.AllocateId();
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true); state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
const auto geometry = state.builder.AllocateId(); state.indirect_format_geometry_function, buffer.format);
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry, const auto field = [&](uint32_t index) {
state.indirect_format_geometry_function, buffer.format); const auto value = state.builder.AllocateId();
const auto field = [&](uint32_t index) { state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
const auto value = state.builder.AllocateId(); return value;
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index); };
return value; const auto bytes = field(0);
}; const auto component = Binary(
const auto bytes = field(0); state, spv::OpUMod, TypeU32(state),
const auto component = Binary( Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
state, spv::OpUMod, TypeU32(state), const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1)); const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3)); Unary(state, spv::OpNot, TypeU64(state), alignment));
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address, const auto address = Binary(
Unary(state, spv::OpNot, TypeU64(state), alignment)); state, spv::OpIAdd, TypeU64(state), base,
const auto address = Binary( Unary(state, spv::OpUConvert, TypeU64(state),
state, spv::OpIAdd, TypeU64(state), base, Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
Unary(state, spv::OpUConvert, TypeU64(state), const auto memory = AndCondition(
Binary(state, spv::OpIMul, TypeU32(state), component, field(2)))); state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
const auto memory = AndCondition( Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)), ConstantU32(state, 4)));
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector, const auto raw = EmitValueOrZeroIfCondition(
ConstantU32(state, 4))); state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
const auto raw = EmitValueOrZeroIfCondition( [&]() {
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)), // Format alignment and component spacing keep every selected component within one DWORD.
[&]() { const auto word = LoadBdaDword(
// Format alignment and component spacing keep every selected component within one DWORD. ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
const auto word = LoadBdaDword( ConstantU64(state, ~uint64_t {3})));
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address, const auto byte = Binary(
ConstantU64(state, ~uint64_t {3}))); state, spv::OpBitwiseAnd, TypeU32(state),
const auto byte = Binary( Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
state, spv::OpBitwiseAnd, TypeU32(state), return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3)); Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word, ConstantU32(state, 3)));
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, });
ConstantU32(state, 3))); const auto result = state.builder.AllocateId();
}); state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
const auto result = state.builder.AllocateId(); state.indirect_format_function, buffer.format, buffer.selector,
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result, component, raw);
state.indirect_format_function, buffer.format, buffer.selector, return result;
component, raw);
return result;
});
} }
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem, uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
@@ -1135,15 +1132,12 @@ uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) { uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state; auto& state = ctx.state;
const auto mem = ctx.Memory(inst); const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
return LoadIndirectFormattedX(ctx, inst);
}
return EmitValueOrDefaultIfCondition( return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components), state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() { ConstantU32CompositeZero(state, components), [&]() {
if (mem.kind == IR::ResourceKind::IndirectBuffer) { if (mem.kind == IR::ResourceKind::IndirectBuffer) {
const auto function = state.indirect_buffer_load_functions.at( const auto function = state.indirect_buffer_load_functions.at(
{mem.data_bits, components, uint32_t(mem.coherent)}); {mem.data_bits, components, uint32_t(mem.coherent), uint32_t(mem.formatted)});
const auto args = IndirectBufferArgs(ctx, inst); const auto args = IndirectBufferArgs(ctx, inst);
const auto result = state.builder.AllocateId(); const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components), state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
@@ -1231,62 +1225,8 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
}); });
} }
} // namespace
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u);
if (!inserted) continue;
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false),
memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineIndirectBufferFormats(ValueEmitContext& ctx) { void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
auto& state = ctx.state; auto& state = ctx.state;
const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) {
return std::ranges::any_of(*block, [&](const IR::Inst& inst) {
if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false;
const auto& memory = ctx.Memory(inst);
return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted;
});
});
if (!needed) return;
const auto type = TypeU32(state); const auto type = TypeU32(state);
constexpr auto format_count = constexpr auto format_count =
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u; static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
state.builder.AddFunction(spv::OpFunctionEnd); state.builder.AddFunction(spv::OpFunctionEnd);
} }
} // namespace
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent),
uint32_t(memory.formatted)}, 0u);
if (!inserted) continue;
if (memory.formatted && state.indirect_format_function == 0u) {
DefineIndirectBufferFormats(ctx);
}
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "") +
(memory.formatted ? "_formatted" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto buffer = PrepareIndirectBuffer(state, args, memory.formatted);
const auto result = memory.formatted ? LoadIndirectFormattedX(ctx, buffer)
: LoadIndirectBuffer(ctx, buffer, memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineGetBdaPointer(EmitterState& state) { void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) { if (!state.program.info.uses_dma) {
return; return;
@@ -675,7 +675,6 @@ void EmitProgram(EmitterState& state) {
} }
DefineGetBdaPointer(state); DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx); DefineIndirectBufferLoads(ctx);
DefineIndirectBufferFormats(ctx);
DefineBvhIntersect(state); DefineBvhIntersect(state);
if (program.info.uses_swizzle) { if (program.info.uses_swizzle) {
ctx.scratch_u32_variable = state.builder.AllocateId(); ctx.scratch_u32_variable = state.builder.AllocateId();
@@ -29,12 +29,14 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto pointer = state.builder.AllocateId(); const auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) { if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
const auto index = inst.Arg(1).U32() / 4u; const auto index = inst.Arg(1).U32() / 4u;
const auto outer = index < 3u; const bool quad = tess.domain == 2u;
EXIT_NOT_IMPLEMENTED(index >= 4u); const auto outer_count = quad ? 4u : 3u;
const bool outer = index < outer_count;
EXIT_NOT_IMPLEMENTED(index >= (quad ? 6u : 4u));
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)), state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
pointer, outer ? variable : state.tess_inner_variable, pointer, outer ? variable : state.tess_inner_variable,
ConstantU32(state, outer ? index : index - 3u)); ConstantU32(state, outer ? index : index - outer_count));
return pointer; return pointer;
} }
auto address = ctx.Arg(inst, 1); auto address = ctx.Arg(inst, 1);
@@ -128,20 +130,25 @@ void DefineTessellationInterfaces(EmitterState& state) {
void DefineTessellationExecutionModes(EmitterState& state) { void DefineTessellationExecutionModes(EmitterState& state) {
const auto& tess = state.input_info.vertex->tess; const auto& tess = state.input_info.vertex->tess;
// CxVgtTfParam: triangles, integer or fractional-odd partitioning, clockwise output. // Triangle or quad domain, integer or fractional partitioning, triangle output.
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || EXIT_NOT_IMPLEMENTED((tess.domain != 1u && tess.domain != 2u) ||
(tess.partitioning != 0u && tess.partitioning != 2u) || (tess.partitioning != 0u && tess.partitioning != 2u && tess.partitioning != 3u) ||
tess.output_topology != 2u); (tess.output_topology != 2u && tess.output_topology != 3u));
state.builder.RequireCapability(spv::CapabilityTessellation); state.builder.RequireCapability(spv::CapabilityTessellation);
if (state.program.stage == ShaderType::TessellationControl) { if (state.program.stage == ShaderType::TessellationControl) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices, state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
tess.output_control_points); tess.output_control_points);
} else { } else {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles); state.builder.AddExecutionMode(state.main_func, tess.domain == 1u
? spv::ExecutionModeTriangles
: spv::ExecutionModeQuads);
state.builder.AddExecutionMode( state.builder.AddExecutionMode(
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
: spv::ExecutionModeSpacingFractionalOdd); : tess.partitioning == 2u ? spv::ExecutionModeSpacingFractionalOdd
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw); : spv::ExecutionModeSpacingFractionalEven);
state.builder.AddExecutionMode(state.main_func, tess.output_topology == 2u
? spv::ExecutionModeVertexOrderCw
: spv::ExecutionModeVertexOrderCcw);
} }
} }
@@ -70,6 +70,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
{0x21u, Opcode::S_SWAPPC_B64}, {0x21u, Opcode::S_SWAPPC_B64},
{0x24u, Opcode::S_AND_SAVEEXEC_B64}, {0x24u, Opcode::S_AND_SAVEEXEC_B64},
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64}, {0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
{0x2cu, Opcode::S_QUADMASK_B32},
{0x2du, Opcode::S_QUADMASK_B64}, {0x2du, Opcode::S_QUADMASK_B64},
{0x34u, Opcode::S_ABS_I32}, {0x34u, Opcode::S_ABS_I32},
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64}, {0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
@@ -517,6 +517,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::S_NOT_B64: case Opcode::S_NOT_B64:
case Opcode::S_WQM_B32: case Opcode::S_WQM_B32:
case Opcode::S_WQM_B64: case Opcode::S_WQM_B64:
case Opcode::S_QUADMASK_B32:
case Opcode::S_QUADMASK_B64: case Opcode::S_QUADMASK_B64:
case Opcode::S_AND_SAVEEXEC_B32: case Opcode::S_AND_SAVEEXEC_B32:
case Opcode::S_ORN2_SAVEEXEC_B32: case Opcode::S_ORN2_SAVEEXEC_B32:
@@ -63,6 +63,7 @@ enum class Opcode {
S_FLBIT_I32, S_FLBIT_I32,
S_FLBIT_I32_I64, S_FLBIT_I32_I64,
S_BITREPLICATE_B64_B32, S_BITREPLICATE_B64_B32,
S_QUADMASK_B32,
S_QUADMASK_B64, S_QUADMASK_B64,
S_GETPC_B64, S_GETPC_B64,
S_SETPC_B64, S_SETPC_B64,
@@ -248,7 +249,9 @@ enum class Opcode {
V_MIN_I32, V_MIN_I32,
V_MAX_I32, V_MAX_I32,
V_MUL_I32_I24, V_MUL_I32_I24,
V_MUL_HI_I32_I24,
V_MUL_U32_U24, V_MUL_U32_U24,
V_MUL_HI_U32_U24,
V_MUL_LO_U32, V_MUL_LO_U32,
V_MUL_HI_U32, V_MUL_HI_U32,
V_MUL_LO_I32, V_MUL_LO_I32,
@@ -257,6 +260,7 @@ enum class Opcode {
V_CVT_PK_U8_F32, V_CVT_PK_U8_F32,
V_MAD_F32, V_MAD_F32,
V_MAD_I16, V_MAD_I16,
V_MAD_U16,
V_MAD_I32_I24, V_MAD_I32_I24,
V_MAD_U32_U24, V_MAD_U32_U24,
V_MAD_U64_U32, V_MAD_U64_U32,
@@ -277,16 +281,20 @@ enum class Opcode {
V_MIN3_I32, V_MIN3_I32,
V_MIN3_U32, V_MIN3_U32,
V_MIN3_F16, V_MIN3_F16,
V_MIN3_I16,
V_MIN3_U16, V_MIN3_U16,
V_MAX3_F32, V_MAX3_F32,
V_MAX3_I32, V_MAX3_I32,
V_MAX3_U32, V_MAX3_U32,
V_MAX3_F16, V_MAX3_F16,
V_MAX3_I16,
V_MAX3_U16,
V_MED3_F32, V_MED3_F32,
V_MED3_I32, V_MED3_I32,
V_MED3_U32, V_MED3_U32,
V_MED3_F16, V_MED3_F16,
V_MED3_I16, V_MED3_I16,
V_MED3_U16,
V_SAD_U32, V_SAD_U32,
V_ADD3_U32, V_ADD3_U32,
V_LSHL_ADD_U32, V_LSHL_ADD_U32,
@@ -354,6 +362,7 @@ enum class Opcode {
V_ASHRREV_I32, V_ASHRREV_I32,
V_LSHLREV_B64, V_LSHLREV_B64,
V_LSHRREV_B64, V_LSHRREV_B64,
V_ASHRREV_I64,
V_LSHLREV_B16, V_LSHLREV_B16,
V_LSHRREV_B16, V_LSHRREV_B16,
V_ASHRREV_I16, V_ASHRREV_I16,
@@ -35,7 +35,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x05u, Opcode::V_SUBREV_F32}, {0x05u, Opcode::V_SUBREV_F32},
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32}, {0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination}, {0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0au, Opcode::V_MUL_HI_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination}, {0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0fu, Opcode::V_MIN_F32}, {0x0fu, Opcode::V_MIN_F32},
{0x10u, Opcode::V_MAX_F32}, {0x10u, Opcode::V_MAX_F32},
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination}, {0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
@@ -157,6 +159,10 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x04u, Opcode::V_CVT_F64_I32}, {0x04u, Opcode::V_CVT_F64_I32},
{0x0fu, Opcode::V_CVT_F32_F64}, {0x0fu, Opcode::V_CVT_F32_F64},
{0x10u, Opcode::V_CVT_F64_F32}, {0x10u, Opcode::V_CVT_F64_F32},
{0x11u, Opcode::V_CVT_F32_UBYTE0},
{0x12u, Opcode::V_CVT_F32_UBYTE1},
{0x13u, Opcode::V_CVT_F32_UBYTE2},
{0x14u, Opcode::V_CVT_F32_UBYTE3},
{0x16u, Opcode::V_CVT_F64_U32}, {0x16u, Opcode::V_CVT_F64_U32},
{0x17u, Opcode::V_TRUNC_F64}, {0x17u, Opcode::V_TRUNC_F64},
{0x18u, Opcode::V_CEIL_F64}, {0x18u, Opcode::V_CEIL_F64},
@@ -167,6 +173,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x07u, Opcode::V_CVT_U32_F32}, {0x07u, Opcode::V_CVT_U32_F32},
{0x08u, Opcode::V_CVT_I32_F32}, {0x08u, Opcode::V_CVT_I32_F32},
{0x0au, Opcode::V_CVT_F16_F32}, {0x0au, Opcode::V_CVT_F16_F32},
{0x0bu, Opcode::V_CVT_F32_F16},
{0x0cu, Opcode::V_CVT_RPI_I32_F32}, {0x0cu, Opcode::V_CVT_RPI_I32_F32},
{0x0du, Opcode::V_CVT_FLR_I32_F32}, {0x0du, Opcode::V_CVT_FLR_I32_F32},
{0x0eu, Opcode::V_CVT_OFF_F32_I4}, {0x0eu, Opcode::V_CVT_OFF_F32_I4},
@@ -334,16 +341,20 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x152u, Opcode::V_MIN3_I32}, {0x152u, Opcode::V_MIN3_I32},
{0x153u, Opcode::V_MIN3_U32}, {0x153u, Opcode::V_MIN3_U32},
{0x351u, Opcode::V_MIN3_F16}, {0x351u, Opcode::V_MIN3_F16},
{0x352u, Opcode::V_MIN3_I16},
{0x353u, Opcode::V_MIN3_U16}, {0x353u, Opcode::V_MIN3_U16},
{0x154u, Opcode::V_MAX3_F32}, {0x154u, Opcode::V_MAX3_F32},
{0x155u, Opcode::V_MAX3_I32}, {0x155u, Opcode::V_MAX3_I32},
{0x156u, Opcode::V_MAX3_U32}, {0x156u, Opcode::V_MAX3_U32},
{0x354u, Opcode::V_MAX3_F16}, {0x354u, Opcode::V_MAX3_F16},
{0x355u, Opcode::V_MAX3_I16},
{0x356u, Opcode::V_MAX3_U16},
{0x157u, Opcode::V_MED3_F32}, {0x157u, Opcode::V_MED3_F32},
{0x158u, Opcode::V_MED3_I32}, {0x158u, Opcode::V_MED3_I32},
{0x159u, Opcode::V_MED3_U32}, {0x159u, Opcode::V_MED3_U32},
{0x357u, Opcode::V_MED3_F16}, {0x357u, Opcode::V_MED3_F16},
{0x358u, Opcode::V_MED3_I16}, {0x358u, Opcode::V_MED3_I16},
{0x359u, Opcode::V_MED3_U16},
{0x15du, Opcode::V_SAD_U32}, {0x15du, Opcode::V_SAD_U32},
{0x15eu, Opcode::V_CVT_PK_U8_F32}, {0x15eu, Opcode::V_CVT_PK_U8_F32},
{0x178u, Opcode::V_XOR3_B32}, {0x178u, Opcode::V_XOR3_B32},
@@ -354,6 +365,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x16cu, Opcode::V_MUL_HI_I32}, {0x16cu, Opcode::V_MUL_HI_I32},
{0x2ffu, Opcode::V_LSHLREV_B64}, {0x2ffu, Opcode::V_LSHLREV_B64},
{0x300u, Opcode::V_LSHRREV_B64}, {0x300u, Opcode::V_LSHRREV_B64},
{0x301u, Opcode::V_ASHRREV_I64},
{0x303u, Opcode::V_ADD_NC_U16}, {0x303u, Opcode::V_ADD_NC_U16},
{0x304u, Opcode::V_SUB_NC_U16}, {0x304u, Opcode::V_SUB_NC_U16},
{0x305u, Opcode::V_MUL_LO_U16}, {0x305u, Opcode::V_MUL_LO_U16},
@@ -370,6 +382,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x311u, Opcode::V_PACK_B32_F16}, {0x311u, Opcode::V_PACK_B32_F16},
{0x314u, Opcode::V_LSHLREV_B16}, {0x314u, Opcode::V_LSHLREV_B16},
{0x319u, Opcode::V_SUBREV_I32}, {0x319u, Opcode::V_SUBREV_I32},
{0x340u, Opcode::V_MAD_U16},
{0x345u, Opcode::V_XAD_U32}, {0x345u, Opcode::V_XAD_U32},
{0x346u, Opcode::V_LSHL_ADD_U32}, {0x346u, Opcode::V_LSHL_ADD_U32},
{0x347u, Opcode::V_ADD_LSHL_U32}, {0x347u, Opcode::V_ADD_LSHL_U32},
@@ -488,8 +501,10 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
} }
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) { bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 || return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 ||
opcode == Opcode::V_MAD_I16; opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 ||
opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16;
} }
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) { bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
@@ -1335,6 +1350,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
case Opcode::V_SUB_NC_I16: case Opcode::V_SUB_NC_I16:
case Opcode::V_LSHLREV_B64: case Opcode::V_LSHLREV_B64:
case Opcode::V_LSHRREV_B64: case Opcode::V_LSHRREV_B64:
case Opcode::V_ASHRREV_I64:
case Opcode::V_LSHLREV_B16: case Opcode::V_LSHLREV_B16:
case Opcode::V_LSHRREV_B16: case Opcode::V_LSHRREV_B16:
case Opcode::V_ASHRREV_I16: case Opcode::V_ASHRREV_I16:
@@ -1523,7 +1539,10 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
bool SupportsNativeVop3Clamp(Opcode opcode) { bool SupportsNativeVop3Clamp(Opcode opcode) {
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
opcode == Opcode::V_MAD_I16; opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 ||
opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 ||
opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MUL_HI_I32_I24 || opcode == Opcode::V_MUL_HI_U32_U24;
} }
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
@@ -1554,7 +1573,17 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
return clamp != 0u || omod != 0u || neg != 0u; return clamp != 0u || omod != 0u || neg != 0u;
} }
switch (opcode) { switch (opcode) {
case Opcode::V_MUL_HI_I32_I24:
case Opcode::V_MUL_HI_U32_U24:
// The high product fits in 32 bits, so clamp has no effect.
return abs != 0u || op_sel != 0u || omod != 0u || neg != 0u;
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u; case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
case Opcode::V_CVT_F32_UBYTE0:
case Opcode::V_CVT_F32_UBYTE1:
case Opcode::V_CVT_F32_UBYTE2:
case Opcode::V_CVT_F32_UBYTE3:
// Byte conversions ignore input modifiers and support all output modifiers.
return op_sel != 0u;
case Opcode::V_INTERP_P1LL_F16: case Opcode::V_INTERP_P1LL_F16:
case Opcode::V_INTERP_P2_F16: case Opcode::V_INTERP_P2_F16:
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u; return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
@@ -20,12 +20,12 @@ void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
Write16Bits(DestinationOperand(inst), result); Write16Bits(DestinationOperand(inst), result);
} }
void Translator::V_MAD_I16(const Decoder::Instruction& inst) { void Translator::Integer16Mad(const Decoder::Instruction& inst, bool sign) {
const auto lhs = ReadU16AsU32(inst.src0, true); const auto lhs = ReadU16AsU32(inst.src0, sign);
const auto rhs = ReadU16AsU32(inst.src1, true); const auto rhs = ReadU16AsU32(inst.src1, sign);
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true)); auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, sign));
if (inst.dst.clamp) { if (inst.dst.clamp) {
result = Saturate16(result, true); result = Saturate16(result, sign);
} }
Write16Bits(DestinationOperand(inst), result); Write16Bits(DestinationOperand(inst), result);
} }
@@ -147,6 +147,12 @@ void Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
const auto arg_type = IR::ArgTypeOf(opcode, index); const auto arg_type = IR::ArgTypeOf(opcode, index);
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index); const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type); args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type);
if (opcode == IR::ValueOpcode::ShiftRightArithmetic64 && index == 0u &&
operand.kind == Decoder::OperandKind::LiteralConstant) {
args[index] = ir.ConstructU64(
ReadU32(operand),
IR::U32(IR::Value((operand.value & 0x80000000u) ? 0xffffffffu : 0u)));
}
if (mask_shift_count && index == 1u) { if (mask_shift_count && index == 1u) {
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u))); args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
} }
@@ -263,7 +269,7 @@ void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
WriteOperand(DestinationOperand(inst), result); WriteOperand(DestinationOperand(inst), result);
} }
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) { void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high) {
const auto extract24 = [&](IR::U32 value) { const auto extract24 = [&](IR::U32 value) {
return IR::U32( return IR::U32(
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract, ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
@@ -271,7 +277,9 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
}; };
const auto lhs = extract24(ReadU32(inst.src0)); const auto lhs = extract24(ReadU32(inst.src0));
const auto rhs = extract24(ReadU32(inst.src1)); const auto rhs = extract24(ReadU32(inst.src1));
auto result = ir.IMul(lhs, rhs); const auto multiply = high ? (sign ? IR::ValueOpcode::SMulHi : IR::ValueOpcode::UMulHi)
: IR::ValueOpcode::IMul32;
auto result = IR::U32(ir.Emit(multiply, {lhs, rhs}));
if (addend) { if (addend) {
result = ir.IAdd(result, ReadU32(inst.src2)); result = ir.IAdd(result, ReadU32(inst.src2));
} }
@@ -369,7 +377,7 @@ void Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high))); WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high)));
} }
void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) { void Translator::S_QUADMASK(const Decoder::Instruction& inst, bool is64) {
const auto compact = [&](IR::U32 value) { const auto compact = [&](IR::U32 value) {
auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u)))); auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u))));
bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u)))); bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u))));
@@ -382,12 +390,17 @@ void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))), ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))),
IR::U32(IR::Value(0xffu))); IR::U32(IR::Value(0xffu)));
}; };
const auto source = ReadU32Pair(inst.src0); IR::U32 quads;
const auto quads = ir.BitwiseOr( if (is64) {
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u)))); const auto source = ReadU32Pair(inst.src0);
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u))); quads = ir.BitwiseOr(
WriteOperand(DestinationOperand(inst), result); compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})}))); WriteOperand(DestinationOperand(inst), ir.ConstructU64(quads, IR::U32(IR::Value(0u))));
} else {
quads = compact(ReadU32(inst.src0));
WriteOperand(DestinationOperand(inst), quads);
}
ir.SetScc(ir.INotEqual(quads, IR::U32(IR::Value(0u))));
} }
void Translator::BFM_B32(const Decoder::Instruction& inst) { void Translator::BFM_B32(const Decoder::Instruction& inst) {
@@ -248,7 +248,8 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_BITSET0_B64: return S_BITSET_B64(inst, false); case O::S_BITSET0_B64: return S_BITSET_B64(inst, false);
case O::S_BITSET1_B64: return S_BITSET_B64(inst, true); case O::S_BITSET1_B64: return S_BITSET_B64(inst, true);
case O::S_BITREPLICATE_B64_B32: return S_BITREPLICATE_B64_B32(inst); case O::S_BITREPLICATE_B64_B32: return S_BITREPLICATE_B64_B32(inst);
case O::S_QUADMASK_B64: return S_QUADMASK_B64(inst); case O::S_QUADMASK_B32: return S_QUADMASK(inst, false);
case O::S_QUADMASK_B64: return S_QUADMASK(inst, true);
case O::S_BFM_B32: return BFM_B32(inst); case O::S_BFM_B32: return BFM_B32(inst);
case O::S_BFM_B64: return S_BFM_B64(inst); case O::S_BFM_B64: return S_BFM_B64(inst);
case O::S_BFE_U32: return S_BFE_U32(inst, false); case O::S_BFE_U32: return S_BFE_U32(inst, false);
@@ -1165,6 +1165,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
: IR::U32(IR::Value(0u)); : IR::U32(IR::Value(0u));
entry_ir.SetExecLo(initial_lo); entry_ir.SetExecLo(initial_lo);
entry_ir.SetExecHi(initial_hi); entry_ir.SetExecHi(initial_hi);
const auto active_lane_count = [&] {
auto count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
if (options.wave_size == 64u) {
count = entry_ir.IAdd(count, IR::U32(entry_ir.Emit(
IR::ValueOpcode::BitCount32, {initial_hi})));
}
return count;
};
if (options.stage == ShaderType::Compute) { if (options.stage == ShaderType::Compute) {
const auto* cs = options.input_info.compute; const auto* cs = options.input_info.compute;
const auto thread_ids = const auto thread_ids =
@@ -1302,7 +1310,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
builtin(IR::StageInputKind::WorkgroupId, 1)); builtin(IR::StageInputKind::WorkgroupId, 1));
} }
} else if (options.stage == ShaderType::Local) { } else if (options.stage == ShaderType::Local) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u))); entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), active_lane_count());
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
builtin(IR::StageInputKind::VertexIndex)); builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u))); entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
@@ -1380,21 +1388,17 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
} else if (options.stage == ShaderType::Vertex) { } else if (options.stage == ShaderType::Vertex) {
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave. // Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
// ESVertCount describes input vertices, independently of lane ordinals. // ESVertCount describes input vertices, independently of lane ordinals.
auto vertex_count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
if (options.wave_size == 64u) {
vertex_count = entry_ir.IAdd(vertex_count, IR::U32(entry_ir.Emit(
IR::ValueOpcode::BitCount32, {initial_hi})));
}
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2), entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
IR::U32(IR::Value(options.wave_size << 12u))); IR::U32(IR::Value(options.wave_size << 12u)));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u)))); entry_ir.BitwiseOr(active_lane_count(), IR::U32(IR::Value(1u << 28u))));
// Vulkan adds an indirect record's bases to the indices; the hardware passes them // Vulkan adds an indirect record's bases to the indices; the hardware passes them
// only in the user SGPRs the packet patches. // only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged;
// it does not make the record's bases zero.
const auto& offsets = options.input_info.vertex->indirect_offsets; const auto& offsets = options.input_info.vertex->indirect_offsets;
const auto index = [&](uint8_t slot, IR::StageInputKind kind, const auto index = [&](uint8_t slot, IR::StageInputKind kind,
IR::StageInputKind base_kind) { IR::StageInputKind base_kind) {
if (offsets == ShaderIndirectOffsets {}) { if (!options.input_info.vertex->indirect_draw) {
return builtin(kind); return builtin(kind);
} }
const auto base = builtin(base_kind); const auto base = builtin(base_kind);
@@ -167,7 +167,7 @@ private:
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic); void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void V_MAD_I16(const Decoder::Instruction& inst); void Integer16Mad(const Decoder::Instruction& inst, bool sign);
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic); bool arithmetic);
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode); void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
@@ -187,7 +187,7 @@ private:
void V_XOR3_B32(const Decoder::Instruction& inst); void V_XOR3_B32(const Decoder::Instruction& inst);
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign); void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign);
void V_FFBH_32(const Decoder::Instruction& inst, bool sign); void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend); void Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high = false);
void V_MAD_U64_U32(const Decoder::Instruction& inst); void V_MAD_U64_U32(const Decoder::Instruction& inst);
void V_SAD_U32(const Decoder::Instruction& inst); void V_SAD_U32(const Decoder::Instruction& inst);
void V_ADD3_U32(const Decoder::Instruction& inst); void V_ADD3_U32(const Decoder::Instruction& inst);
@@ -196,7 +196,7 @@ private:
void V_BCNT_U32_B32(const Decoder::Instruction& inst); void V_BCNT_U32_B32(const Decoder::Instruction& inst);
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low); void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst); void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
void S_QUADMASK_B64(const Decoder::Instruction& inst); void S_QUADMASK(const Decoder::Instruction& inst, bool is64);
void BFM_B32(const Decoder::Instruction& inst); void BFM_B32(const Decoder::Instruction& inst);
IR::U32 RightMask32(IR::U32 count); IR::U32 RightMask32(IR::U32 count);
void S_BFM_B64(const Decoder::Instruction& inst); void S_BFM_B64(const Decoder::Instruction& inst);
@@ -375,9 +375,14 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_SUB_NC_U16: case O::V_SUB_NC_U16:
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false); case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false); case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
case O::V_MAD_I16: return V_MAD_I16(inst); case O::V_MAD_I16: return Integer16Mad(inst, true);
case O::V_MAD_U16: return Integer16Mad(inst, false);
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true); case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false); case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
case O::V_MIN3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMinTri32, true);
case O::V_MAX3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMaxTri32, true);
case O::V_MAX3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMaxTri32, false);
case O::V_MED3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMedTri32, false);
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true); case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true); case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false); case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
@@ -565,6 +570,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_LSHRREV_B64: case O::V_LSHRREV_B64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true, return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true,
false, false); false, false);
case O::V_ASHRREV_I64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightArithmetic64, IR::Type::U64, true,
false, false);
case O::V_XNOR_B32: case O::V_XNOR_B32:
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false); return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false);
@@ -578,6 +586,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MAD_U32_U24: return Integer24(inst, false, true); case O::V_MAD_U32_U24: return Integer24(inst, false, true);
case O::V_MUL_I32_I24: return Integer24(inst, true, false); case O::V_MUL_I32_I24: return Integer24(inst, true, false);
case O::V_MUL_U32_U24: return Integer24(inst, false, false); case O::V_MUL_U32_U24: return Integer24(inst, false, false);
case O::V_MUL_HI_I32_I24: return Integer24(inst, true, false, true);
case O::V_MUL_HI_U32_U24: return Integer24(inst, false, false, true);
case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst); case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst);
case O::V_SAD_U32: return V_SAD_U32(inst); case O::V_SAD_U32: return V_SAD_U32(inst);
case O::V_ADD3_U32: return V_ADD3_U32(inst); case O::V_ADD3_U32: return V_ADD3_U32(inst);
+7 -9
View File
@@ -291,10 +291,9 @@ static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserS
const auto& metadata = data.vertex; const auto& metadata = data.vertex;
if (metadata.vertex_attrib_reg >= 0) { if (metadata.vertex_attrib_reg >= 0) {
info.fetch_external = false; info.has_dynamic_vertex_fetch = true;
info.fetch_embedded = true; info.fetch_attrib_reg = metadata.vertex_attrib_reg;
info.fetch_attrib_reg = metadata.vertex_attrib_reg; info.fetch_buffer_reg = metadata.vertex_buffer_reg;
info.fetch_buffer_reg = metadata.vertex_buffer_reg;
const auto* attrib = reinterpret_cast<const uint32_t*>( const auto* attrib = reinterpret_cast<const uint32_t*>(
static_cast<uint64_t>(user_sgpr.value[metadata.vertex_attrib_reg]) | static_cast<uint64_t>(user_sgpr.value[metadata.vertex_attrib_reg]) |
@@ -413,7 +412,7 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) { void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX); EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
key.clear(); key.clear();
key.push_back(static_cast<uint32_t>(info.fetch_embedded)); key.push_back(static_cast<uint32_t>(info.has_dynamic_vertex_fetch));
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg)); key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg)); key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
key.push_back(info.resources_num); key.push_back(info.resources_num);
@@ -422,7 +421,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
key.push_back(info.lds_size_dwords); key.push_back(info.lds_size_dwords);
key.push_back(info.pa_cl_vs_out_cntl); key.push_back(info.pa_cl_vs_out_cntl);
key.push_back(info.user_clip_planes); key.push_back(info.user_clip_planes);
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u)); key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u) |
(uint32_t {info.indirect_draw} << 16u));
key.push_back(static_cast<uint32_t>(info.clip_space.enabled)); key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
if (info.clip_space.enabled) { if (info.clip_space.enabled) {
for (const float value: info.clip_space.scale) { for (const float value: info.clip_space.scale) {
@@ -693,9 +693,7 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
LOGF("ShaderDbgDumpInputInfo()\n"); LOGF("ShaderDbgDumpInputInfo()\n");
LOGF("\t fetch_external = %s\n" LOGF("\t has_dynamic_vertex_fetch = %s\n", info.has_dynamic_vertex_fetch ? "true" : "false");
"\t fetch_embedded = %s\n",
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false");
for (int i = 0; i < info.resources_num; i++) { for (int i = 0; i < info.resources_num; i++) {
LOGF("\t input %d\n", i); LOGF("\t input %d\n", i);
+5 -4
View File
@@ -125,8 +125,7 @@ struct ShaderTessellationInputInfo {
}; };
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance // GS user-data slots an indirect draw packet writes its record's base vertex and first instance
// to. Like the hardware index VGPRs, the vertex shader's indices exclude both bases once either // to. A packet can leave both slots unused even when its record contains nonzero offsets.
// slot is set.
struct ShaderIndirectOffsets { struct ShaderIndirectOffsets {
static constexpr uint8_t None = UINT8_MAX; static constexpr uint8_t None = UINT8_MAX;
@@ -159,8 +158,10 @@ struct ShaderVertexInputInfo {
ShaderMeshInputInfo mesh; ShaderMeshInputInfo mesh;
ShaderTessellationInputInfo tess; ShaderTessellationInputInfo tess;
ShaderIndirectOffsets indirect_offsets; ShaderIndirectOffsets indirect_offsets;
bool fetch_external = false; // GPU-read records add Vulkan bases even when the packet patches no user SGPRs.
bool fetch_embedded = false; bool indirect_draw = false;
// Attribute-table metadata enables dynamic fetch detection; loads are matched later.
bool has_dynamic_vertex_fetch = false;
}; };
struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo { struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
+55 -10
View File
@@ -6,6 +6,7 @@
#include "libs/ajm/decoder.h" #include "libs/ajm/decoder.h"
#include "libs/ajm/mp3_decoder.h" #include "libs/ajm/mp3_decoder.h"
#include "libs/ajm/opus_decoder.h" #include "libs/ajm/opus_decoder.h"
#include "libs/ajm/resampler.h"
#include "libs/audio.h" #include "libs/audio.h"
#include "libs/errno.h" #include "libs/errno.h"
#include "libs/libs.h" #include "libs/libs.h"
@@ -63,12 +64,6 @@ struct AjmSidebandMFrame {
uint32_t reserved; uint32_t reserved;
}; };
struct AjmSidebandResampleInfo {
float ratio;
int32_t num_samples;
uint32_t reserved[8];
};
struct AjmGetStatisticsResult { struct AjmGetStatisticsResult {
AjmSidebandResult result; AjmSidebandResult result;
float engine_usage_batch; float engine_usage_batch;
@@ -104,6 +99,8 @@ constexpr uint64_t AJM_FLAG_RUN_GET_CODEC_INFO = 1ull << 11u;
constexpr uint64_t AJM_FLAG_RUN_MULTIPLE_FRAMES = 1ull << 12u; constexpr uint64_t AJM_FLAG_RUN_MULTIPLE_FRAMES = 1ull << 12u;
constexpr uint64_t AJM_FLAG_CONTROL_RESET = 1ull << 13u; constexpr uint64_t AJM_FLAG_CONTROL_RESET = 1ull << 13u;
constexpr uint64_t AJM_FLAG_CONTROL_INITIALIZE = 1ull << 14u; constexpr uint64_t AJM_FLAG_CONTROL_INITIALIZE = 1ull << 14u;
constexpr uint64_t AJM_FLAG_CONTROL_RESAMPLE = 1ull << 15u;
constexpr uint64_t AJM_FLAG_CONTROL_RESAMPLE_EX = 1ull << 16u;
constexpr uint64_t AJM_FLAG_SIDEBAND_RESAMPLE_INFO = 1ull << 44u; constexpr uint64_t AJM_FLAG_SIDEBAND_RESAMPLE_INFO = 1ull << 44u;
constexpr uint64_t AJM_FLAG_SIDEBAND_GAPLESS = 1ull << 45u; constexpr uint64_t AJM_FLAG_SIDEBAND_GAPLESS = 1ull << 45u;
constexpr uint64_t AJM_FLAG_SIDEBAND_FORMAT = 1ull << 46u; constexpr uint64_t AJM_FLAG_SIDEBAND_FORMAT = 1ull << 46u;
@@ -177,6 +174,7 @@ struct AjmInstanceState {
uint64_t flags = 0; uint64_t flags = 0;
AjmGaplessState gapless; AjmGaplessState gapless;
std::unique_ptr<AjmDecoder> decoder; std::unique_ptr<AjmDecoder> decoder;
AjmResampler resampler;
}; };
static std::mutex g_ajm_instances_mutex; static std::mutex g_ajm_instances_mutex;
@@ -358,7 +356,7 @@ static void AjmWriteSideband(uint64_t flags, void* sideband_output, size_t sideb
offset + AJM_SIDEBAND_RESAMPLE_INFO_SIZE <= sideband_output_size) { offset + AJM_SIDEBAND_RESAMPLE_INFO_SIZE <= sideband_output_size) {
auto* info = reinterpret_cast<AjmSidebandResampleInfo*>( auto* info = reinterpret_cast<AjmSidebandResampleInfo*>(
static_cast<uint8_t*>(sideband_output) + offset); static_cast<uint8_t*>(sideband_output) + offset);
info->ratio = 1.0f; *info = decode_result.resample_info;
offset += AJM_SIDEBAND_RESAMPLE_INFO_SIZE; offset += AJM_SIDEBAND_RESAMPLE_INFO_SIZE;
} }
@@ -425,6 +423,12 @@ static AjmDecodeResult AjmDecodeInstance(uint32_t instance, const void* input, s
return result; return result;
} }
// The resampler requires one complete decoded packet per decoder call.
if (state->codec == static_cast<uint32_t>(AjmCodec::DecOpus) &&
(state->flags & AJM_INSTANCE_FLAG_RESAMPLE) != 0) {
return state->resampler.Decode(*state->decoder, state->flags, input, input_size, output,
output_size, multiple_frames, &state->gapless);
}
return state->decoder->Decode(input, input_size, output, output_size, multiple_frames, return state->decoder->Decode(input, input_size, output, output_size, multiple_frames,
&state->gapless); &state->gapless);
} }
@@ -514,6 +518,32 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
sideband_input = static_cast<const uint8_t*>(sideband_input) + AJM_SIDEBAND_GAPLESS_SIZE; sideband_input = static_cast<const uint8_t*>(sideband_input) + AJM_SIDEBAND_GAPLESS_SIZE;
sideband_input_size -= AJM_SIDEBAND_GAPLESS_SIZE; sideband_input_size -= AJM_SIDEBAND_GAPLESS_SIZE;
} }
for (const auto flag : {AJM_FLAG_CONTROL_RESAMPLE, AJM_FLAG_CONTROL_RESAMPLE_EX}) {
if ((flags & flag) == 0) {
continue;
}
const bool extended = flag == AJM_FLAG_CONTROL_RESAMPLE_EX;
const size_t size = extended ? 12 : 8;
if (sideband_input == nullptr || sideband_input_size < size ||
(state->flags & AJM_INSTANCE_FLAG_RESAMPLE) == 0) {
result.result = AJM_RESULT_INVALID_PARAMETER;
return result;
}
float ratio, change = 0;
uint32_t resample_flags;
const auto* data = static_cast<const uint8_t*>(sideband_input);
std::memcpy(&ratio, data, sizeof(ratio));
if (extended) {
std::memcpy(&change, data + sizeof(ratio), sizeof(change));
}
std::memcpy(&resample_flags, data + size - sizeof(resample_flags), sizeof(resample_flags));
result.result = state->resampler.SetParameters(ratio, change, resample_flags);
if (result.result != OK) {
return result;
}
sideband_input = data + size;
sideband_input_size -= size;
}
if ((flags & AJM_FLAG_CONTROL_RESET) != 0) { if ((flags & AJM_FLAG_CONTROL_RESET) != 0) {
state->gapless.Reset(); state->gapless.Reset();
decoder->Reset(); decoder->Reset();
@@ -524,6 +554,7 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
result = decoder->Initialize(sideband_input, sideband_input_size); result = decoder->Initialize(sideband_input, sideband_input_size);
} }
result.resample_info = state->resampler.GetInfo();
return result; return result;
} }
@@ -572,6 +603,7 @@ static AjmSidebandFormat AjmGetInstanceFormat(uint32_t instance,
if (out_result != nullptr) { if (out_result != nullptr) {
*out_result = state->decoder->MakeResult(); *out_result = state->decoder->MakeResult();
out_result->format = state->decoder->GetFormat(); out_result->format = state->decoder->GetFormat();
out_result->resample_info = state->resampler.GetInfo();
} }
return state->decoder->GetFormat(); return state->decoder->GetFormat();
} }
@@ -921,7 +953,13 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParameters(AjmBatchInfo* info, uint32_t
float ratio, uint32_t flags, void* result) { float ratio, uint32_t flags, void* result) {
PRINT_NAME(); PRINT_NAME();
EXIT_NOT_IMPLEMENTED(info == nullptr); EXIT_NOT_IMPLEMENTED(info == nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE); const struct {
float ratio;
uint32_t flags;
} params {ratio, flags};
const auto decoded = AjmControlInstance(instance, AJM_FLAG_CONTROL_RESAMPLE, &params,
sizeof(params), nullptr, nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decoded);
LOGF("\t instance = 0x%08" PRIx32 ", ratio = %f, flags = 0x%08" PRIx32 "\n", instance, LOGF("\t instance = 0x%08" PRIx32 ", ratio = %f, flags = 0x%08" PRIx32 "\n", instance,
static_cast<double>(ratio), flags); static_cast<double>(ratio), flags);
return AjmAppendJob(info, AJM_JOB_SET_RESAMPLE_PARAMETERS_SIZE, "set-resample"); return AjmAppendJob(info, AJM_JOB_SET_RESAMPLE_PARAMETERS_SIZE, "set-resample");
@@ -933,7 +971,14 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParametersEx(AjmBatchInfo* info, uint32_
void* result) { void* result) {
PRINT_NAME(); PRINT_NAME();
EXIT_NOT_IMPLEMENTED(info == nullptr); EXIT_NOT_IMPLEMENTED(info == nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE); const struct {
float ratio;
float change;
uint32_t flags;
} params {ratio_start, ratio_change_per_sample, flags};
const auto decoded = AjmControlInstance(instance, AJM_FLAG_CONTROL_RESAMPLE_EX, &params,
sizeof(params), nullptr, nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decoded);
LOGF("\t instance = 0x%08" PRIx32 ", ratio_start = %f, ratio_change = %f, flags = 0x%08" PRIx32 LOGF("\t instance = 0x%08" PRIx32 ", ratio_start = %f, ratio_change = %f, flags = 0x%08" PRIx32
"\n", "\n",
instance, static_cast<double>(ratio_start), static_cast<double>(ratio_change_per_sample), instance, static_cast<double>(ratio_start), static_cast<double>(ratio_change_per_sample),
@@ -952,7 +997,7 @@ int KYTY_SYSV_ABI AjmBatchJobGetResampleInfo(AjmBatchInfo* info, uint32_t instan
if (result != nullptr) { if (result != nullptr) {
auto* resample = reinterpret_cast<AjmSidebandResampleInfo*>(static_cast<uint8_t*>(result) + auto* resample = reinterpret_cast<AjmSidebandResampleInfo*>(static_cast<uint8_t*>(result) +
AJM_SIDEBAND_RESULT_SIZE); AJM_SIDEBAND_RESULT_SIZE);
resample->ratio = 1.0f; *resample = decode_result.resample_info;
} }
LOGF("\t instance = 0x%08" PRIx32 "\n", instance); LOGF("\t instance = 0x%08" PRIx32 "\n", instance);
return AjmAppendJob(info, AJM_JOB_RUN_SIZE, "get-resample-info"); return AjmAppendJob(info, AJM_JOB_RUN_SIZE, "get-resample-info");
+15 -8
View File
@@ -56,15 +56,22 @@ enum class AjmSampleEncoding : uint32_t {
Float = 2, Float = 2,
}; };
struct AjmSidebandResampleInfo {
float ratio = 1.0f;
int32_t num_samples = 0;
uint32_t reserved[8] {};
};
struct AjmDecodeResult { struct AjmDecodeResult {
int32_t result = OK; int32_t result = OK;
int32_t internal_result = OK; int32_t internal_result = OK;
size_t input_consumed = 0; size_t input_consumed = 0;
size_t output_written = 0; size_t output_written = 0;
uint64_t total_decoded_samples = 0; uint64_t total_decoded_samples = 0;
uint32_t frames = 0; uint32_t frames = 0;
uint32_t frames_per_packet = 0; uint32_t frames_per_packet = 0;
AjmSidebandFormat format {}; AjmSidebandFormat format {};
AjmSidebandResampleInfo resample_info {};
}; };
constexpr int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001; constexpr int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001;
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include "libs/ajm/decoder.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
namespace Libs::Audio::Ajm {
constexpr uint64_t AJM_INSTANCE_FLAG_GAPLESS_LOOP = 1ull << 10u;
constexpr uint64_t AJM_INSTANCE_FLAG_RESAMPLE = 1ull << 12u;
constexpr uint64_t AJM_INSTANCE_FLAG_SEAMLESS_LOOP = 1ull << 13u;
constexpr uint32_t AJM_RESAMPLE_FLAG_RESET = 1u;
constexpr uint32_t AJM_RESAMPLE_FLAG_END = 2u;
constexpr uint32_t AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER = 8u;
class AjmResampler {
public:
int32_t SetParameters(float ratio, float change, uint32_t flags) {
if ((flags & AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER) == 0 &&
(!std::isfinite(ratio) || ratio < 0 || ratio > 4 || !std::isfinite(change))) {
return AJM_RESULT_INVALID_PARAMETER;
}
if ((flags & AJM_RESAMPLE_FLAG_RESET) != 0) {
m_pcm.clear();
m_position = 0;
m_end = false;
}
if ((flags & AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER) == 0) {
m_ratio = ratio;
m_change = std::abs(change) < 0.000001f ? 0 : change;
}
m_end |= (flags & AJM_RESAMPLE_FLAG_END) != 0;
return OK;
}
[[nodiscard]] AjmSidebandResampleInfo GetInfo() const {
AjmSidebandResampleInfo info;
info.ratio = static_cast<float>(m_ratio);
info.num_samples = m_end && m_position >= Samples()
? -1
: static_cast<int32_t>(std::ceil(Samples() - m_position));
return info;
}
AjmDecodeResult Decode(AjmDecoder& decoder, uint64_t flags, const void* input,
size_t input_size, void* output, size_t output_size,
bool multiple_frames, AjmGaplessState* gapless) {
auto result = decoder.MakeResult();
if (result.format.channel_num == 0) {
return decoder.Decode(input, input_size, output, output_size, multiple_frames, gapless);
}
bool stop_decoding = false;
for (;;) {
const auto format = decoder.GetFormat();
const auto encoding = static_cast<AjmSampleEncoding>(format.sample_encoding);
const auto sample_bytes = format.channel_num * AjmBytesPerSample(encoding);
if (!m_pcm.empty() && sample_bytes != m_sample_bytes) {
result.result = AJM_RESULT_INVALID_PARAMETER;
break;
}
if (output == nullptr || output_size - result.output_written < sample_bytes) {
if (m_position < Samples() || result.input_consumed < input_size) {
result.result |= AJM_RESULT_NOT_ENOUGH_ROOM;
}
break;
}
const auto index = static_cast<size_t>(m_position);
const auto fraction = m_position - static_cast<double>(index);
const bool need_next = fraction != 0 && index + 1 >= Samples();
if (index >= Samples() || need_next) {
Compact();
if (!stop_decoding && input != nullptr && result.input_consumed < input_size &&
(gapless == nullptr || !gapless->IsEnd())) {
// A decode job can produce up to six 20 ms Opus frames in one packet.
const auto channels = std::max<uint32_t>(format.channel_num, flags & 0x7fu);
m_frame.resize(5760 * channels * AjmBytesPerSample(encoding));
auto decoded =
decoder.Decode(static_cast<const uint8_t*>(input) + result.input_consumed,
input_size - result.input_consumed, m_frame.data(),
m_frame.size(), false, gapless);
result.input_consumed += decoded.input_consumed;
result.frames += decoded.frames;
result.frames_per_packet = decoded.frames_per_packet;
result.total_decoded_samples = decoded.total_decoded_samples;
result.format = decoded.format;
result.result |= decoded.result;
result.internal_result = decoded.internal_result;
m_sample_bytes = decoded.format.channel_num * AjmBytesPerSample(encoding);
m_pcm.insert(m_pcm.end(), m_frame.begin(),
m_frame.begin() + decoded.output_written);
stop_decoding =
!multiple_frames || decoded.result != OK || decoded.input_consumed == 0;
if (gapless != nullptr && gapless->IsEnd() &&
(flags & AJM_INSTANCE_FLAG_GAPLESS_LOOP) != 0) {
gapless->Reset();
stop_decoding |= (flags & AJM_INSTANCE_FLAG_SEAMLESS_LOOP) == 0;
}
continue;
}
if (!m_end || m_position >= Samples()) {
if (!m_end && !stop_decoding && (gapless == nullptr || !gapless->IsEnd())) {
result.result |= AJM_RESULT_PARTIAL_INPUT;
}
break;
}
}
const auto current = static_cast<size_t>(m_position);
const auto next = std::min(current + 1, Samples() - 1);
auto* dst = static_cast<uint8_t*>(output) + result.output_written;
const auto* a = m_pcm.data() + current * sample_bytes;
const auto* b = m_pcm.data() + next * sample_bytes;
const auto weight = m_position - static_cast<double>(current);
if (weight == 0) {
std::memcpy(dst, a, sample_bytes);
} else {
switch (encoding) {
case AjmSampleEncoding::S16:
Interpolate<int16_t>(dst, a, b, format.channel_num, weight);
break;
case AjmSampleEncoding::S32:
Interpolate<int32_t>(dst, a, b, format.channel_num, weight);
break;
case AjmSampleEncoding::Float:
Interpolate<float>(dst, a, b, format.channel_num, weight);
break;
}
}
result.output_written += sample_bytes;
m_position += m_ratio;
m_ratio = std::clamp(m_ratio + m_change, 0.0, 4.0);
if (m_ratio == 0 || m_ratio == 4) {
m_change = 0;
}
}
Compact();
result.resample_info = GetInfo();
return result;
}
private:
template <typename T>
static void Interpolate(uint8_t* dst, const uint8_t* a, const uint8_t* b, uint32_t channels,
double weight) {
for (uint32_t channel = 0; channel < channels; ++channel) {
T first, second;
std::memcpy(&first, a + channel * sizeof(T), sizeof(T));
std::memcpy(&second, b + channel * sizeof(T), sizeof(T));
const auto value =
static_cast<T>(first + (static_cast<double>(second) - first) * weight);
std::memcpy(dst + channel * sizeof(T), &value, sizeof(T));
}
}
[[nodiscard]] size_t Samples() const {
return m_sample_bytes == 0 ? 0 : m_pcm.size() / m_sample_bytes;
}
void Compact() {
const auto consumed = std::min(static_cast<size_t>(m_position), Samples());
const auto bytes = consumed * m_sample_bytes;
if (bytes != 0) {
std::memmove(m_pcm.data(), m_pcm.data() + bytes, m_pcm.size() - bytes);
m_pcm.resize(m_pcm.size() - bytes);
m_position -= static_cast<double>(consumed);
}
}
std::vector<uint8_t> m_pcm;
std::vector<uint8_t> m_frame;
size_t m_sample_bytes = 0;
double m_position = 0;
double m_ratio = 1;
double m_change = 0;
bool m_end = false;
};
} // namespace Libs::Audio::Ajm
+56 -17
View File
@@ -546,6 +546,7 @@ struct Ngs2VoiceInternal {
}; };
struct Module { struct Module {
std::vector<uint8_t> param, work, state; std::vector<uint8_t> param, work, state;
std::vector<float> channel_matrix;
void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr; void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr;
std::array<uintptr_t, 3> user_data {}; std::array<uintptr_t, 3> user_data {};
uint32_t flags = 1; uint32_t flags = 1;
@@ -563,6 +564,7 @@ struct Ngs2VoiceInternal {
std::vector<std::vector<float>> matrices; std::vector<std::vector<float>> matrices;
std::vector<Module> modules; std::vector<Module> modules;
std::vector<float> samples; std::vector<float> samples;
std::vector<float> mix_samples;
std::vector<float> output_matrix; std::vector<float> output_matrix;
uint32_t channels = 0; uint32_t channels = 0;
uint32_t output_id = 0; uint32_t output_id = 0;
@@ -614,6 +616,27 @@ struct Ngs2VoiceInternal {
return samples.data() + size_t(id) * grain * channels; return samples.data() + size_t(id) * grain * channels;
} }
void MixChannels(const std::vector<float>& matrix, uint32_t buffer, uint32_t grain) {
if (matrix.empty()) {
return;
}
EXIT_NOT_IMPLEMENTED(channels > 37);
auto* input = Buffer(buffer, grain);
mix_samples.assign(size_t(channels) * grain, 0.0f);
for (uint32_t dst = 0; dst < channels; ++dst) {
for (uint32_t src = 0; src < channels; ++src) {
const float level = matrix[dst * 37 + src];
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) {
mix_samples[dst * grain + i] += input[src * grain + i] * level;
}
}
}
std::copy(mix_samples.begin(), mix_samples.end(), input);
}
void ResetSetupState() { void ResetSetupState() {
SetEvent(4); SetEvent(4);
std::ranges::fill(ports, Port {}); std::ranges::fill(ports, Port {});
@@ -1850,6 +1873,10 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
for (size_t m = 0; m < voice.modules.size(); ++m) { for (size_t m = 0; m < voice.modules.size(); ++m) {
const auto& info = custom.module[m]; const auto& info = custom.module[m];
auto& module = voice.modules[m]; auto& module = voice.modules[m];
if (info.module_id == 0x17) {
voice.MixChannels(module.channel_matrix, info.source_buffer_id, grain);
continue;
}
if (info.module_id != 0x19 && info.module_id != 0x1f) { if (info.module_id != 0x19 && info.module_id != 0x1f) {
if (!module.render_warned) { if (!module.render_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf( Log::WriteToConsoleAndLog(fmt::sprintf(
@@ -1934,7 +1961,8 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info); EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
const auto& output = buffer_info[voice->output_id]; const auto& output = buffer_info[voice->output_id];
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr); EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
EXIT_NOT_IMPLEMENTED((output.waveform_type != 0x12 && output.waveform_type != 0x18) || EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x12 && output.waveform_type != 0x18);
EXIT_NOT_IMPLEMENTED(voice->rack->type == Ngs2RackType::Mastering &&
output.num_channels != voice->channels); output.num_channels != voice->channels);
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels *
(output.waveform_type == 0x12 ? sizeof(int16_t) (output.waveform_type == 0x12 ? sizeof(int16_t)
@@ -1954,6 +1982,9 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
const float level = voice->output_matrix.empty() const float level = voice->output_matrix.empty()
? (src == dst ? 1.0f : 0.0f) ? (src == dst ? 1.0f : 0.0f)
: voice->output_matrix.at(src * output.num_channels + dst); : voice->output_matrix.at(src * output.num_channels + dst);
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) { for (uint32_t i = 0; i < grain; ++i) {
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level; pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
} }
@@ -2452,7 +2483,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id; voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id;
auto& module = voice->modules[index]; auto& module = voice->modules[index];
if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) || if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) ||
(module_id != 0x19 && module_id != 0x1f)) { (module_id != 0x17 && module_id != 0x19 && module_id != 0x1f)) {
if (!module.control_warned) { if (!module.control_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf( Log::WriteToConsoleAndLog(fmt::sprintf(
"warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n", "warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n",
@@ -2461,6 +2492,16 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
} }
break; break;
} }
if (module_id == 0x17) {
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceParamHeader) +
37 * 37 * sizeof(float));
const auto* levels = reinterpret_cast<const float*>(param + 1);
module.channel_matrix.resize(37 * 37);
std::transform(levels, levels + module.channel_matrix.size(),
module.channel_matrix.begin(),
[](float level) { return std::clamp(level, -32.0f, 32.0f); });
break;
}
if (module_id == 0x19) { if (module_id == 0x19) {
struct FxParam { struct FxParam {
Ngs2VoiceParamHeader header; Ngs2VoiceParamHeader header;
@@ -2660,24 +2701,22 @@ int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* stat
submixer->voice_state.state_flags = Ngs2GetStateFlags(voice); submixer->voice_state.state_flags = Ngs2GetStateFlags(voice);
break; break;
} }
case Ngs2RackType::CustomMastering: { case Ngs2RackType::CustomMastering:
const auto configured_size =
voice->rack->option.custom_mastering.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomMasteringVoiceState));
EXIT_NOT_IMPLEMENTED(state_size != configured_size);
std::memset(state, 0, state_size);
auto* mastering = reinterpret_cast<Ngs2CustomMasteringVoiceState*>(state);
mastering->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::CustomSampler: { case Ngs2RackType::CustomSampler: {
const bool sampler = voice->rack->type == Ngs2RackType::CustomSampler;
const auto configured_size = const auto configured_size =
voice->rack->option.custom_sampler.custom_rack_option.state_size; voice->rack->option.custom_sampler.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomSamplerVoiceState)); EXIT_NOT_IMPLEMENTED(configured_size < (sampler ? sizeof(Ngs2CustomSamplerVoiceState)
EXIT_NOT_IMPLEMENTED(state_size != configured_size); : sizeof(Ngs2CustomMasteringVoiceState)));
std::memset(state, 0, state_size); if (state_size < configured_size) {
auto* sampler = reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state); return static_cast<int32_t>(0x804a8011u);
Ngs2FillSamplerState(*voice, *sampler); }
std::memset(state, 0, configured_size);
if (sampler) {
Ngs2FillSamplerState(*voice, *reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state));
} else {
state->state_flags = Ngs2GetStateFlags(voice);
}
break; break;
} }
case Ngs2RackType::Sampler: { case Ngs2RackType::Sampler: {
+360
View File
@@ -0,0 +1,360 @@
#include "libs/ajm.cpp"
#include <array>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <opus.h>
using namespace Libs::Audio::Ajm;
namespace {
void Check(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "AjmResampleTests: %s\n", message);
std::abort();
}
}
struct DecodeResult {
AjmSidebandResult result;
AjmSidebandStream stream;
AjmSidebandMFrame frames;
};
struct Fixture {
uint32_t instance = 0;
uint32_t channels;
std::array<uint8_t, 4096> batch_data {};
AjmBatchInfo batch {};
explicit Fixture(uint32_t channels = 1, AjmSampleEncoding encoding = AjmSampleEncoding::S16,
uint64_t flags = 0x1000)
: channels(channels) {
Check(AjmInstanceCreate(1, 24, flags | channels | (uint64_t(encoding) << 7), &instance) ==
OK,
"create Opus instance");
Initialize();
}
~Fixture() { AjmInstanceDestroy(1, instance); }
AjmBatchInfo* Batch() {
batch = {batch_data.data(), 0, batch_data.size(), nullptr, nullptr};
return &batch;
}
void Initialize() {
AjmDecOpusInitializeParameters params {channels, 48000, 0};
AjmSidebandResult result {};
AjmBatchJobInitialize(Batch(), instance, &params, sizeof(params), &result);
Check(result.result == OK, "initialize Opus instance");
}
void Parameters(float ratio, uint32_t flags, float change = 0, bool extended = false) {
AjmSidebandResult result {};
if (extended) {
AjmBatchJobSetResampleParametersEx(Batch(), instance, ratio, change, flags, &result);
} else {
AjmBatchJobSetResampleParameters(Batch(), instance, ratio, flags, &result);
}
Check(result.result == OK, "set resampling parameters");
}
AjmSidebandResampleInfo Info() {
struct {
AjmSidebandResult result;
AjmSidebandResampleInfo info;
} result {};
AjmBatchJobGetResampleInfo(Batch(), instance, &result);
Check(result.result.result == OK, "get resampling info");
return result.info;
}
void Gapless(uint32_t samples, uint16_t skip) {
AjmSidebandGaplessDecode params {samples, skip, 0};
AjmSidebandResult result {};
AjmBatchJobSetGaplessDecode(Batch(), instance, &params, 1, &result);
Check(result.result == OK, "set gapless parameters");
}
DecodeResult Decode(const void* input, size_t size, void* output, size_t capacity) {
DecodeResult result {};
AjmBatchJobDecode(Batch(), instance, input, size, output, capacity, &result);
Check((result.result.result & ~(AJM_RESULT_NOT_ENOUGH_ROOM | AJM_RESULT_PARTIAL_INPUT)) ==
0,
"decode returned unexpected error");
return result;
}
};
std::vector<uint8_t> Encode(uint32_t channels, int packets) {
int error = 0;
auto* encoder = opus_encoder_create(48000, channels, OPUS_APPLICATION_AUDIO, &error);
Check(encoder != nullptr && error == OPUS_OK, "create test encoder");
std::vector<uint8_t> stream;
std::vector<int16_t> pcm(960 * channels);
std::array<uint8_t, 4096> packet;
for (int p = 0; p < packets; ++p) {
for (size_t i = 0; i < pcm.size(); ++i) {
pcm[i] = static_cast<int16_t>(
12000 * std::sin((i / channels + p * 960) * 0.041 + (i % channels)));
}
const int size = opus_encode(encoder, pcm.data(), 960, packet.data(), packet.size());
Check(size > 0, "encode test packet");
stream.push_back(static_cast<uint8_t>(size));
stream.push_back(static_cast<uint8_t>(size >> 8));
stream.insert(stream.end(), packet.begin(), packet.begin() + size);
}
opus_encoder_destroy(encoder);
return stream;
}
void SmallOutputs(const std::vector<uint8_t>& packet, uint16_t skip = 0) {
Fixture f;
f.Gapless(960 - skip, skip);
f.Parameters(1, 3);
std::array<uint8_t, 1030> output;
output.fill(0x5a);
auto first = f.Decode(packet.data(), packet.size(), output.data(), 1024);
Check(first.stream.size_consumed == static_cast<int>(packet.size()),
"small output must consume packet");
Check(first.stream.size_produced == 1024, "small output must produce 1024 bytes");
Check(first.frames.num_frames == 1 && first.stream.total_decoded_samples == 960 - skip,
"decoded frame counted once before resampling");
Check(f.Info().num_samples == 448 - skip, "report retained source samples");
Check(std::all_of(output.begin() + 1024, output.end(), [](auto v) { return v == 0x5a; }),
"do not write past output capacity");
const auto last = f.Decode(nullptr, 0, output.data(), 1024);
Check(last.result.result == OK && last.stream.size_consumed == 0 &&
last.stream.size_produced == 896 - skip * 2 && last.frames.num_frames == 0 &&
last.stream.total_decoded_samples == first.stream.total_decoded_samples,
"END drains retained PCM without decoding or reporting partial input");
Check(f.Decode(nullptr, 0, output.data(), 1024).stream.size_produced == 0,
"END drains only once");
}
void UnityMatchesWholeDecode(uint32_t channels, AjmSampleEncoding encoding) {
const auto stream = Encode(channels, 6);
const auto sample_bytes = channels * AjmBytesPerSample(encoding);
Fixture reference(channels, encoding, 0);
reference.Gapless(5328, 312);
std::vector<uint8_t> expected(5760 * sample_bytes);
const auto full =
reference.Decode(stream.data(), stream.size(), expected.data(), expected.size());
expected.resize(full.stream.size_produced);
Check(expected.size() == 5328 * sample_bytes, "reference gapless length");
Fixture f(channels, encoding);
f.Gapless(5328, 312);
f.Parameters(1, 3);
std::vector<uint8_t> actual;
std::array<uint8_t, 1025> output;
size_t offset = 0;
uint32_t frames = 0;
for (int i = 0; i < 100; ++i) {
const auto decoded = f.Decode(offset < stream.size() ? stream.data() + offset : nullptr,
stream.size() - offset, output.data(), output.size());
offset += decoded.stream.size_consumed;
frames += decoded.frames.num_frames;
actual.insert(actual.end(), output.begin(), output.begin() + decoded.stream.size_produced);
if (decoded.stream.size_produced == 0) {
break;
}
}
Check(actual == expected && frames == 6 && offset == stream.size(),
"chunked PCM must exactly match whole decode, without loss or duplication");
}
void ChunkedRates(const std::vector<uint8_t>& stream, uint32_t samples, uint16_t skip) {
for (float ratio: {0.125f, 0.749153f, 1.334840f}) {
Fixture reference;
reference.Gapless(samples, skip);
reference.Parameters(ratio, 3);
std::vector<uint8_t> expected(
(static_cast<size_t>(std::ceil(samples / double(ratio))) + 1) * 2);
const auto full =
reference.Decode(stream.data(), stream.size(), expected.data(), expected.size());
expected.resize(full.stream.size_produced);
Check(expected.size() == static_cast<size_t>(std::ceil(samples / double(ratio))) * 2,
"fractional resampling rounds up final output sample count");
Fixture f;
f.Gapless(samples, skip);
f.Parameters(ratio, 1);
std::vector<uint8_t> actual;
std::array<uint8_t, 1024> output;
size_t offset = 0;
for (int i = 0; i < 1000; ++i) {
if (offset == stream.size()) {
// The caller can signal END only after submitting the last packet.
f.Parameters(0, 2 | 8);
}
const auto decoded = f.Decode(offset < stream.size() ? stream.data() + offset : nullptr,
stream.size() - offset, output.data(), output.size());
offset += decoded.stream.size_consumed;
actual.insert(actual.end(), output.begin(),
output.begin() + decoded.stream.size_produced);
if (decoded.stream.size_produced == 0) {
break;
}
}
Check(actual == expected && offset == stream.size(),
"non-unity PCM is independent of output chunk boundaries and deferred END");
}
}
void LoopBoundaries() {
const auto loop = Encode(1, 2);
auto stream = loop;
stream.insert(stream.end(), loop.begin(), loop.end());
for (uint64_t flags: {0x1400u, 0x3400u}) {
Fixture f(1, AjmSampleEncoding::S16, flags);
f.Gapless(1500, 100);
f.Parameters(1, 1);
std::array<uint8_t, 8192> pcm;
const auto first = f.Decode(stream.data(), stream.size(), pcm.data(), 1024);
Check(first.stream.size_produced == 1024, "loop first partial output");
const auto second =
f.Decode(stream.data() + first.stream.size_consumed,
stream.size() - first.stream.size_consumed, pcm.data(), pcm.size());
const bool seamless = flags == 0x3400;
Check(second.stream.size_produced == (seamless ? 6000 - 1024 : 3000 - 1024),
"non-seamless loop stops job at stream boundary");
const auto consumed = first.stream.size_consumed + second.stream.size_consumed;
Check(consumed == static_cast<int>(seamless ? stream.size() : loop.size()),
"loop boundary input consumption");
if (!seamless) {
Check(
f.Decode(stream.data() + consumed, stream.size() - consumed, pcm.data(), pcm.size())
.stream.size_produced == 3000,
"next job resumes gapless loop");
}
}
}
void RatesAndControls(const std::vector<uint8_t>& packet) {
for (float ratio: {0.125f, 0.5f, 1.0f, 1.25f, 2.0f, 4.0f}) {
Fixture f;
f.Parameters(ratio, 3);
std::vector<int16_t> pcm(8000);
const auto result = f.Decode(packet.data(), packet.size(), pcm.data(), pcm.size() * 2);
Check(result.stream.size_produced == static_cast<int>(std::ceil(960.0 / ratio)) * 2,
"resampling ratio controls output duration");
Check(result.stream.total_decoded_samples == 960,
"ratio does not change decoded sample count");
}
Fixture f;
f.Parameters(1, 1);
std::array<int16_t, 512> pcm;
f.Decode(packet.data(), packet.size(), pcm.data(), sizeof(pcm));
f.Initialize();
Check(f.Info().num_samples == 448, "codec initialize must preserve resampler PCM");
f.Parameters(0, 0);
const auto frozen = f.Decode(nullptr, 0, pcm.data(), sizeof(pcm));
Check(
frozen.stream.size_produced == sizeof(pcm) && f.Info().num_samples == 448 &&
std::all_of(pcm.begin(), pcm.end(), [&](auto sample) { return sample == pcm.front(); }),
"zero ratio repeats one sample without advancing input");
f.Parameters(1, 0, 0.25f, true);
f.Parameters(std::numeric_limits<float>::quiet_NaN(), 8);
f.Decode(nullptr, 0, pcm.data(), 4 * sizeof(int16_t));
Check(f.Info().ratio == 2.0f, "ignore preserves ratio ramp");
f.Parameters(1, 1);
Check(f.Info().num_samples == 0, "resample RESET discards retained PCM");
Check(f.Decode(nullptr, 0, pcm.data(), sizeof(pcm)).stream.size_produced == 0,
"RESET leaves no stale audio");
}
void UnbufferedAndSplit(const std::vector<uint8_t>& packet) {
Fixture raw(1, AjmSampleEncoding::S16, 0);
std::array<uint8_t, 1024> pcm;
const auto too_small = raw.Decode(packet.data(), packet.size(), pcm.data(), pcm.size());
Check(too_small.result.result == AJM_RESULT_NOT_ENOUGH_ROOM &&
too_small.stream.size_consumed == 0 && too_small.stream.size_produced == 0,
"ordinary decoding still requires room for a whole packet");
Fixture f;
f.Parameters(1, 3);
AjmBuffer input[] {{const_cast<uint8_t*>(packet.data()), 1},
{const_cast<uint8_t*>(packet.data()) + 1, packet.size() - 1}};
AjmBuffer output[] {{pcm.data(), 257}, {pcm.data() + 257, 767}};
DecodeResult result {};
AjmBatchJobDecodeSplit(f.Batch(), f.instance, input, 2, output, 2, &result);
Check(result.stream.size_produced == 1024 &&
result.stream.size_consumed == static_cast<int>(packet.size()),
"split buffers retain PCM across jobs");
}
void FormatChangeRequiresReset() {
Fixture f(2);
f.Parameters(1, 1);
const auto stereo = Encode(2, 1);
std::array<uint8_t, 4096> pcm;
f.Decode(stereo.data(), stereo.size(), pcm.data(), 1024);
f.channels = 1;
f.Initialize();
DecodeResult result {};
AjmBatchJobDecode(f.Batch(), f.instance, nullptr, 0, pcm.data(), pcm.size(), &result);
Check(result.result.result == AJM_RESULT_INVALID_PARAMETER && result.stream.size_produced == 0,
"format change cannot reinterpret retained PCM with a different channel stride");
f.Parameters(1, 3);
const auto mono = Encode(1, 1);
Check(f.Decode(mono.data(), mono.size(), pcm.data(), pcm.size()).stream.size_produced == 1920,
"resampler RESET permits a new stream format");
}
void EndPersistsUntilReset(const std::vector<uint8_t>& packet) {
Fixture f;
f.Parameters(0.5f, 1);
std::array<int16_t, 64> pcm;
auto decoded = f.Decode(packet.data(), packet.size(), pcm.data(), sizeof(pcm));
size_t produced = decoded.stream.size_produced;
f.Parameters(0.5f, 2);
for (int i = 0; i < 40; ++i) {
f.Parameters(0.5f, 0);
decoded = f.Decode(nullptr, 0, pcm.data(), sizeof(pcm));
produced += decoded.stream.size_produced;
Check((decoded.result.result & AJM_RESULT_PARTIAL_INPUT) == 0,
"END remains active across subsequent ratio controls");
if (f.Info().num_samples < 0) {
break;
}
}
Check(produced == 3840 && f.Info().num_samples < 0,
"fully drained END reports a negative retained sample count");
f.Parameters(1, 1);
Check(f.Info().num_samples == 0, "RESET clears end-of-stream state");
}
} // namespace
int main(int argc, char** argv) {
const auto packet = Encode(1, 1);
SmallOutputs(packet);
SmallOutputs(packet, 312);
for (uint32_t channels: {1u, 2u}) {
for (auto encoding:
{AjmSampleEncoding::S16, AjmSampleEncoding::S32, AjmSampleEncoding::Float}) {
UnityMatchesWholeDecode(channels, encoding);
}
}
RatesAndControls(packet);
UnbufferedAndSplit(packet);
ChunkedRates(Encode(1, 6), 5328, 312);
LoopBoundaries();
FormatChangeRequiresReset();
EndPersistsUntilReset(packet);
if (argc >= 2) {
std::ifstream file(argv[1], std::ios::binary);
std::vector<uint8_t> real_packet((std::istreambuf_iterator<char>(file)), {});
Check(real_packet.size() == 209, "expected real 209-byte packet fixture");
SmallOutputs(real_packet, 312);
}
if (argc >= 3) {
std::ifstream file(argv[2], std::ios::binary);
std::vector<uint8_t> real_stream((std::istreambuf_iterator<char>(file)), {});
Check(real_stream.size() == 4048, "expected complete real stream fixture");
ChunkedRates(real_stream, 25920, 312);
}
std::puts("AjmResampleTests: passed");
}
+124 -19
View File
@@ -751,6 +751,26 @@ void TestCustomSideChainUserFx() {
"user FX did not receive the side-chain buffer after the source channels"); "user FX did not receive the side-chain buffer after the source channels");
} }
struct RenderRack {
std::unique_ptr<std::byte[]> storage;
uintptr_t handle = 0;
RenderRack(Ngs2Internal& system, uint32_t id, const Ngs2RackOption& option) {
Ngs2ContextBufferInfo buffer {};
Check(Ngs2RackQueryBufferSize(id, &option, &buffer) == OK, "rack size query failed");
storage = std::make_unique<std::byte[]>(buffer.host_buffer_size);
buffer.host_buffer = storage.get();
Check(Ngs2RackCreate(reinterpret_cast<uintptr_t>(&system), id, &option, &buffer,
&handle) == OK, "render rack creation failed");
}
~RenderRack() { Check(Ngs2RackDestroy(handle, nullptr) == OK, "render rack cleanup failed"); }
Ngs2VoiceInternal& Voice() {
return *reinterpret_cast<Ngs2VoiceInternal*>(reinterpret_cast<Ngs2RackInternal*>(handle) + 1);
}
};
void TestPcm16RenderOutput() { void TestPcm16RenderOutput() {
Ngs2Internal system; Ngs2Internal system;
system.option = Ngs2DefaultSystemOption(); system.option = Ngs2DefaultSystemOption();
@@ -760,23 +780,11 @@ void TestPcm16RenderOutput() {
Ngs2FillDefaultRackOption(0x3000, &mastering_option); Ngs2FillDefaultRackOption(0x3000, &mastering_option);
sampler_option.common.max_voices = 2; sampler_option.common.max_voices = 2;
sampler_option.common.max_ports = 1; sampler_option.common.max_ports = 1;
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal) RenderRack sampler(system, 0x1000, sampler_option.common);
std::byte sampler_storage[sizeof(Ngs2RackInternal) + 2 * sizeof(Ngs2VoiceInternal)]; RenderRack master(system, 0x3000, mastering_option.common);
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
std::byte mastering_storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
Ngs2ContextBufferInfo sampler_buffer {sampler_storage, sizeof(sampler_storage)};
Ngs2ContextBufferInfo mastering_buffer {mastering_storage, sizeof(mastering_storage)};
uintptr_t sampler_handle = 0, mastering_handle = 0;
const auto system_handle = reinterpret_cast<uintptr_t>(&system); const auto system_handle = reinterpret_cast<uintptr_t>(&system);
Check(Ngs2RackCreate(system_handle, 0x1000, &sampler_option.common, &sampler_buffer, auto* samplers = &sampler.Voice();
&sampler_handle) == OK && auto& mastering = master.Voice();
Ngs2RackCreate(system_handle, 0x3000, &mastering_option.common,
&mastering_buffer, &mastering_handle) == OK,
"PCM16 render rack creation failed");
auto* samplers = reinterpret_cast<Ngs2VoiceInternal*>(
reinterpret_cast<Ngs2RackInternal*>(sampler_handle) + 1);
auto& mastering = *reinterpret_cast<Ngs2VoiceInternal*>(
reinterpret_cast<Ngs2RackInternal*>(mastering_handle) + 1);
mastering.channels = 1; mastering.channels = 1;
mastering.state = Ngs2VoicePlayState::Playing; mastering.state = Ngs2VoicePlayState::Playing;
const int16_t source[2][4] = {{16384, -16384, 32767, -32768}, const int16_t source[2][4] = {{16384, -16384, 32767, -32768},
@@ -797,9 +805,104 @@ void TestPcm16RenderOutput() {
Check(Ngs2SystemRender(system_handle, &output, 1) == OK && Check(Ngs2SystemRender(system_handle, &output, 1) == OK &&
std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }), std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }),
"PCM16 output retained samples from the previous render"); "PCM16 output retained samples from the previous render");
Check(Ngs2RackDestroy(mastering_handle, nullptr) == OK && }
Ngs2RackDestroy(sampler_handle, nullptr) == OK,
"PCM16 render rack cleanup failed"); void TestCustomMasteringChannelMixer() {
Ngs2Internal system;
system.option = Ngs2DefaultSystemOption();
system.option.num_grain_samples = 6;
Ngs2RackOptionUnion sampler_option {};
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
sampler_option.common.max_voices = 1;
sampler_option.common.max_ports = 1;
Ngs2CustomMasteringRackOption master_option {};
auto& custom = master_option.custom_rack_option;
custom.rack_option.size = sizeof(master_option);
custom.rack_option.max_voices = 1;
custom.rack_option.max_voice_channels = 6;
custom.num_buffers = 1;
custom.num_modules = 1;
custom.module[0].module_id = 0x17;
RenderRack sampler(system, 0x1000, sampler_option.common);
RenderRack master(system, 0x4003, custom.rack_option);
auto& source = sampler.Voice();
auto& output = master.Voice();
output.channels = 6;
output.SetEvent(1);
struct {
Ngs2VoiceParamHeader header {5484, 0, 0x40001700};
float levels[37][37] {};
} matrix;
constexpr float center = 0.70710677f;
matrix.levels[0][0] = matrix.levels[1][1] = 1.0f;
matrix.levels[0][2] = matrix.levels[1][2] = center;
matrix.levels[0][4] = matrix.levels[1][5] = center;
int16_t impulses[6][6] {};
for (uint32_t c = 0; c < 6; ++c) impulses[c][c] = 8192;
for (unsigned pass = 0; pass < 2; ++pass) {
if (pass == 1) {
std::memset(matrix.levels, 0, sizeof(matrix.levels));
matrix.levels[0][1] = 64.0f;
matrix.levels[1][0] = -64.0f;
}
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&output), &matrix.header) == OK,
"custom channel mixer control failed");
source.SetupSampler({0x12, 6, 48000});
source.blocks.push_back({reinterpret_cast<const uint8_t*>(impulses),
{0, sizeof(impulses), 0, 0, 6}});
source.ports[0].dest = &output;
source.SetEvent(1);
std::array<float, 14> pcm;
pcm.fill(99.0f);
Ngs2RenderBufferInfo buffer {pcm.data() + 1, 12 * sizeof(float), 0x18, 2};
Check(Ngs2SystemRender(reinterpret_cast<uintptr_t>(&system), &buffer, 1) == OK,
"custom mastering rejected stereo output");
const std::array<float, 12> expected = pass == 0
? std::array<float, 12> {0.25f, 0, 0, 0.25f, center / 4, center / 4,
0, 0, center / 4, 0, 0, center / 4}
: std::array<float, 12> {0, -8, 8, 0};
Check(pcm.front() == 99 && pcm.back() == 99 &&
std::equal(expected.begin(), expected.end(), pcm.begin() + 1),
"custom mixer lost channel routing, in-place input, clamping or output bounds");
}
}
void TestCustomVoiceStateCapacity() {
for (auto type: {Ngs2RackType::CustomSampler, Ngs2RackType::CustomMastering}) {
Fixture f(48000, 1, Ngs2RackType::CustomSampler);
f.rack.type = type;
f.rack.option.custom_sampler.custom_rack_option.state_size = 212;
f.voice.state_flags = 3;
f.voice.decoded_samples = 37;
f.voice.decoded_bytes = 128;
alignas(Ngs2CustomSamplerVoiceState) std::array<uint8_t, 384> storage;
auto* state = reinterpret_cast<Ngs2VoiceState*>(storage.data() + 8);
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
for (size_t capacity: {212u, 368u}) {
storage.fill(0xa5);
Check(Ngs2VoiceGetState(handle, state, capacity) == OK && state->state_flags == 3 &&
state->error_code == 0 &&
std::all_of(storage.begin(), storage.begin() + 8,
[](uint8_t v) { return v == 0xa5; }) &&
std::all_of(storage.begin() + 220, storage.end(),
[](uint8_t v) { return v == 0xa5; }),
"oversized custom state buffer was rejected or overwritten past configured state");
if (type == Ngs2RackType::CustomSampler) {
const auto* sampler = reinterpret_cast<const Ngs2CustomSamplerVoiceState*>(state);
Check(sampler->num_decoded_samples == 37 && sampler->decoded_data_size == 128,
"custom sampler state omitted playback progress");
}
const size_t base_size = type == Ngs2RackType::CustomSampler
? sizeof(Ngs2CustomSamplerVoiceState) : sizeof(Ngs2CustomMasteringVoiceState);
Check(std::all_of(storage.begin() + 8 + base_size, storage.begin() + 220,
[](uint8_t v) { return v == 0; }),
"configured custom state extension was not initialized");
}
storage.fill(0xa5);
Check(Ngs2VoiceGetState(handle, state, 211) == static_cast<int32_t>(0x804a8011u) &&
std::ranges::all_of(storage, [](uint8_t v) { return v == 0xa5; }),
"undersized custom state buffer was accepted or modified");
}
} }
void TestPitchLoopAndSkip() { void TestPitchLoopAndSkip() {
@@ -1700,6 +1803,8 @@ int main() {
TestAtrac9Vibration(); TestAtrac9Vibration();
TestAtrac9Discrete(); TestAtrac9Discrete();
TestPcm16RenderOutput(); TestPcm16RenderOutput();
TestCustomMasteringChannelMixer();
TestCustomVoiceStateCapacity();
TestSubmixerSetupAndRouting(); TestSubmixerSetupAndRouting();
TestCustomSideChainUserFx(); TestCustomSideChainUserFx();
TestWaveformBlockGeometry(); TestWaveformBlockGeometry();
File diff suppressed because it is too large. Load diff
+84 -2
View File
@@ -1938,7 +1938,7 @@ void TestVertexBufferGrouping() {
Check(input.fetch_attrib_reg == 2, "a draw reread the vertex metadata of a mapped shader"); Check(input.fetch_attrib_reg == 2, "a draw reread the vertex metadata of a mapped shader");
ShaderMapUserData(regs.es_regs.data_addr, mapped); ShaderMapUserData(regs.es_regs.data_addr, mapped);
(void)PrepareProgram(regs, HW::Context{}, HW::UserConfig{}, input); (void)PrepareProgram(regs, HW::Context{}, HW::UserConfig{}, input);
Check(!input.fetch_embedded, "remapping a shader kept its previous vertex metadata"); Check(!input.has_dynamic_vertex_fetch, "remapping a shader kept its previous vertex metadata");
} }
void TestNggVertexEntryState() { void TestNggVertexEntryState() {
@@ -2397,6 +2397,50 @@ void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() {
CheckSpirvBinaryValidates(result.spirv); CheckSpirvBinaryValidates(result.spirv);
} }
void TestVop3FloatConversionDecode() {
namespace Decoder = ShaderRecompiler::Decoder;
const std::pair<uint32_t, Decoder::Opcode> conversions[] = {
{0x18b, Decoder::Opcode::V_CVT_F32_F16},
{0x191, Decoder::Opcode::V_CVT_F32_UBYTE0},
{0x192, Decoder::Opcode::V_CVT_F32_UBYTE1},
{0x193, Decoder::Opcode::V_CVT_F32_UBYTE2},
{0x194, Decoder::Opcode::V_CVT_F32_UBYTE3},
};
for (const auto &[encoding, opcode] : conversions) {
for (const uint32_t source : {261u, 12u, 128u, 255u}) {
const uint32_t code[] = {
EncodeVop3Word0(encoding, 20, 0, 1, true),
EncodeVop3Word1(source, 0, 0) | (2u << 27u) | (1u << 29u),
0xa5a53e00u,
};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == opcode && inst.family == Decoder::Family::VOP3 &&
inst.src_count == 1 && inst.word_count == (source == 255u ? 3u : 2u),
"VOP3 conversion lost its opcode, unary source, or literal length");
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 20 &&
inst.dst.sdwa_sel == 6 && inst.dst.clamp && inst.dst.omod == 2,
"VOP3 conversion lost its full-width destination or output modifiers");
const bool half = opcode == Decoder::Opcode::V_CVT_F32_F16;
Check(inst.src0.absolute == half && inst.src0.negate == half,
"VOP3 conversion must apply half-float modifiers and ignore byte modifiers");
Decoder::Operand expected_source;
Decoder::DecodeScalarSource(source, 0, expected_source);
Check(inst.src0.kind == expected_source.kind && inst.src0.reg == expected_source.reg &&
(source != 255u || inst.src0.value == code[2]),
"VOP3 conversion changed its source register or literal value");
}
for (const uint32_t op_sel : {1u, 2u, 4u, 8u}) {
const uint32_t code[] = {EncodeVop3Word0(encoding, 20, op_sel),
EncodeVop3Word1(261, 0, 0)};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == Decoder::Opcode::UNSUPPORTED,
"VOP3 conversion accepted unsupported OP_SEL bits");
}
}
}
void TestNewShaderRecompilerMoreAluFamilies() { void TestNewShaderRecompilerMoreAluFamilies() {
const uint32_t shader[] = { const uint32_t shader[] = {
EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7 EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7
@@ -10550,6 +10594,43 @@ void TestNewShaderRecompilerNativeU64Translation() {
"captured VOP3 V_LSHLREV_B64 was not present in the decoded dump"); "captured VOP3 V_LSHLREV_B64 was not present in the decoded dump");
CheckSpirvBinaryValidates(result.spirv); CheckSpirvBinaryValidates(result.spirv);
const uint32_t ashrrev_i64_shader[] = {
0xd7010001u, 0x0000d50cu,
EncodeSopp(0x01),
};
Decoder::Instruction decoded_ashrrev_i64;
ShaderRecompiler::Decoder::DecodeInstruction(ashrrev_i64_shader, 0u,
decoded_ashrrev_i64);
Check(decoded_ashrrev_i64.opcode == Decoder::Opcode::V_ASHRREV_I64 &&
decoded_ashrrev_i64.dst.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_i64.dst.reg == 1u &&
decoded_ashrrev_i64.src_count == 2u &&
decoded_ashrrev_i64.src0.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_i64.src0.reg == 12u &&
decoded_ashrrev_i64.src1.kind == Decoder::OperandKind::VccLo,
"decoder rejected VOP3 V_ASHRREV_I64 fields");
result = RecompileForTest(ashrrev_i64_shader, options);
Check((result.decoded_dump.find("V_ASHRREV_I64 v1, v12, vcc_lo") != std::string::npos),
"VOP3 V_ASHRREV_I64 was not present in the decoded dump");
CheckSpirvBinaryValidates(result.spirv);
const uint32_t ashrrev_captured[] = {0xd7010007u, 0x00020498u, EncodeSopp(0x01)};
Decoder::Instruction decoded_ashrrev_captured;
ShaderRecompiler::Decoder::DecodeInstruction(ashrrev_captured, 0u,
decoded_ashrrev_captured);
Check(decoded_ashrrev_captured.opcode == Decoder::Opcode::V_ASHRREV_I64 &&
decoded_ashrrev_captured.family == Decoder::Family::VOP3 &&
decoded_ashrrev_captured.src_count == 2u &&
decoded_ashrrev_captured.dst.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_captured.dst.reg == 7u &&
decoded_ashrrev_captured.src0.kind == Decoder::OperandKind::IntegerInlineConstant &&
decoded_ashrrev_captured.src0.value == 24u &&
decoded_ashrrev_captured.src1.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_captured.src1.reg == 2u,
"captured VOP3 V_ASHRREV_I64 opcode 0x301 was not decoded");
result = RecompileForTest(ashrrev_captured, options);
CheckSpirvBinaryValidates(result.spirv);
const uint32_t cmpx_i64_shader[] = { const uint32_t cmpx_i64_shader[] = {
0xd4b5007eu, 0x00020e80u, // v_cmpx_ne_i64 exec, 0, v[7:8] 0xd4b5007eu, 0x00020e80u, // v_cmpx_ne_i64 exec, 0, v[7:8]
EncodeSopp(0x01), EncodeSopp(0x01),
@@ -11575,7 +11656,7 @@ void TestEmbeddedFetchPreservesSharedScalarLoad() {
reinterpret_cast<uint64_t>(attributes.data())}; reinterpret_cast<uint64_t>(attributes.data())};
std::memcpy(user_data.data() + 12, tables, sizeof(tables)); std::memcpy(user_data.data() + 12, tables, sizeof(tables));
ShaderVertexInputInfo input{}; ShaderVertexInputInfo input{};
input.fetch_embedded = true; input.has_dynamic_vertex_fetch = true;
input.fetch_buffer_reg = 12; input.fetch_buffer_reg = 12;
input.fetch_attrib_reg = 14; input.fetch_attrib_reg = 14;
input.resources_num = 1; input.resources_num = 1;
@@ -17121,6 +17202,7 @@ int main() {
using namespace Libs::Graphics; using namespace Libs::Graphics;
EnsureConfigInitialized(); EnsureConfigInitialized();
TestVop3FloatConversionDecode();
TestRayTracingInstructions(); TestRayTracingInstructions();
TestResourceDescriptorClassification(); TestResourceDescriptorClassification();
TestShaderBufferResourceSize(); TestShaderBufferResourceSize();