Compare commits

..
Author SHA1 Message Date
bc4333e313 ngs2: size 16-bit PCM blocks in Ngs2CalcWaveformBlock (The Binding of Isaac: Repentance) (#1968)
Ngs2CalcWaveformBlock returned NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT with a
zeroed block for 16-bit PCM (0x12). The Binding of Isaac: Repentance
(PPSA03311) ignores the error and queues the zeroed blocks, which the
sampler skips, so the game had no audio at all. PCM frames have a fixed
size, so the block now starts at sample_pos frames and spans num_samples
frames.

Fixes #667

Co-authored-by: gl-ungus <notasecondemail63@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 05:39:49 +01:00
nmzik b0c8229bd4 docs: update the README status, Cyberpunk 2077 screenshot and notable forks 2026-10-30 05:17:52 +01:00
nmzik bc1c779972 launcher: put the icon on a blue tile
Icon based on designs by Ant_IV (Discord), earlher (Discord) and cosmo (Discord, GitHub), closest reference first.
2026-10-30 05:03:22 +01:00
Artyom Zabroda 0c2546e850 shader: lower whole-wave OR scans to subgroup reductions 2026-10-30 04:31:24 +01:00
nmzik eb3cb47296 renderer: drop the per-draw scalar-read overlap guard 2026-10-30 04:19:05 +01:00
nmzikandArtyom Zabroda a5cfdcfd32 texture_cache: apply DCC fast clears of mipmapped color targets
Co-authored-by: Artyom Zabroda <artyomzabroda@gmail.com>
2026-10-30 04:05:24 +01:00
nmzik ae67e2db51 shader: hash only the program code and literal buffer 2026-10-30 03:29:45 +01:00
nmzik 1703836d06 shader: type packed integer vertex inputs by their component type 2026-10-30 02:59:36 +01:00
31 changed files with 406 additions and 244 deletions

No files matched your search

+8 -4
View File
@@ -23,11 +23,8 @@ can change significantly between builds.
## Current Status
KytyPS5 can boot 2D and 3D games, including titles built with Unreal Engine
4/5, Unity, and custom engines. External low-level emulation modules are neither required nor
planned.
Development is currently focused on expanding game compatibility and improving boot reliability.
External low-level emulation modules are neither required nor planned.
Windows and Linux are the primary target platforms and receive the most testing.
@@ -90,6 +87,13 @@ or graphical glitches, so please include the version you tested when reporting a
<p align="center"><em>And many more...</em></p>
## Notable Forks
These community forks bring KytyPS5 to other platforms.
- [Cosmo/KytyPS5](https://github.com/Cosmo/KytyPS5) — Xbox Series X: Direct3D 12 renderer, sleek
controller-first interface, and local multiplayer for up to four players.
## Contributing
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
+12 -9
View File
@@ -1,15 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
<linearGradient id="tile" x2="0" y2="1"><stop stop-color="#3d7fe6"/><stop offset="1" stop-color="#1c47b0"/></linearGradient>
<linearGradient id="body" x2="0" y2="1"><stop stop-color="#fff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
<linearGradient id="gloss" x2="0" y2="1"><stop stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
<rect x="16" y="16" width="480" height="480" rx="112" fill="url(#tile)"/>
<path d="M16 128 A112 112 0 0 1 128 16 L384 16 A112 112 0 0 1 496 128 L496 200 Q256 150 16 236 Z" fill="url(#gloss)"/>
<g transform="translate(256 268) scale(0.86) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
<g transform="translate(0 18)" fill-opacity="0.22" stroke="#000" stroke-opacity="0.22" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/><path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z"/></g>
<g fill="url(#body)" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
<path d="M128 164 L137 128 L174 156 Z" fill="#0b0d12"/><path d="M384 164 L375 128 L338 156 Z" fill="#0b0d12"/>
<g fill="none" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="16"/>
<g fill="none" stroke="#0b0d12" stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" fill="none" stroke="#0b0d12" stroke-width="7"/>
<path d="M168 230 Q194 202 220 230" fill="none" stroke="#0b0d12" stroke-width="15"/>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)" stroke="#0b0d12" stroke-width="16"/>
<g fill="none" stroke="#0b0d12"><g stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" stroke-width="7"/>
<path d="M168 230 Q194 202 220 230" stroke-width="15"/></g>
<circle cx="322" cy="220" r="22" fill="#0b0d12"/>
<path d="M238 252 L274 252 L256 276 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
</g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+48 -38
View File
@@ -259,6 +259,17 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) {
return false;
}
static uint32_t FirstTailLevel(const MipTailLayout& tail, uint32_t width, uint32_t height,
uint32_t levels) {
for (uint32_t level = 0; levels > 1 && level < levels; ++level) {
if (ShiftCeil(width, level) <= tail.width_limit &&
ShiftCeil(height, level) <= tail.height_limit && levels - level <= tail.max_levels) {
return level;
}
}
return levels;
}
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) {
out = {bytes, 1, 1};
@@ -336,23 +347,14 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu
TileSurfaceLayout result {};
result.description = description;
result.texture = texture;
result.first_tail_level = description.levels;
const uint32_t block_slices =
volume ? ShiftCeil(description.depth, std::countr_zero(block.block_depth))
: description.layers;
MipTailLayout tail {};
const bool has_tail = GetMipTailLayout(block, tail);
if (has_tail && description.levels > 1) {
for (uint32_t level = 0; level < description.levels; ++level) {
if (ShiftCeil(width0, level) <= tail.width_limit &&
ShiftCeil(height0, level) <= tail.height_limit &&
description.levels - level <= tail.max_levels) {
result.first_tail_level = level;
break;
}
}
}
result.first_tail_level = GetMipTailLayout(block, tail)
? FirstTailLevel(tail, width0, height0, description.levels)
: description.levels;
for (uint32_t level = 0; level < result.first_tail_level; ++level) {
auto& mip = result.mips[level];
@@ -1213,44 +1215,52 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
return true;
}
static bool TileGetColorMetadataSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t block_width, uint32_t block_height,
TileSizeAlign& total_size) {
if (width == 0 || height == 0 || slices == 0) {
return false;
}
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
const uint64_t blocks = blocks_x * blocks_y;
if (blocks > UINT32_MAX / 4096u / slices) {
return false;
}
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
total_size.align = 4096;
return true;
}
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
TileSizeAlign& total_size, uint32_t num_fragments_log2, TileSizeOffset* mips) {
total_size = {};
if (levels != 1 || num_fragments_log2 != 0 ||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
TileBlockLayout block {};
MipTailLayout tail {};
if (slices == 0 || levels > 16 || num_fragments_log2 != 0 ||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth) ||
!TileGetBlockLayout(tile == Prospero::TileMode::kDepth ? TileBlockFamily::Depth64KB
: TileBlockFamily::RenderTarget64KB,
bytes_per_element, block) ||
!GetMipTailLayout(block, tail)) {
return false;
}
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data.
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. Each slice stores
// its mips backwards through the first tail mip, which fits one block; later tail mips have
// none. Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u),
1u << (coverage_bits / 2u), total_size);
uint64_t slice_size = 0;
for (uint32_t level = std::min(FirstTailLevel(tail, width, height, levels) + 1u, levels);
level-- > 0;) {
const uint64_t size = uint64_t {ShiftCeil(width, level + (coverage_bits + 1u) / 2u)} *
ShiftCeil(height, level + coverage_bits / 2u) * 4096u;
if (mips != nullptr) {
mips[level] = {.size = static_cast<uint32_t>(size),
.offset = static_cast<uint32_t>(slice_size)};
}
slice_size += size;
}
if (slice_size == 0 || slice_size > UINT32_MAX / slices) {
return false;
}
total_size = {static_cast<uint32_t>(slice_size * slices), 4096};
return true;
}
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
TileSizeAlign& total_size) {
total_size = {};
// Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels.
return levels == 1 && TileGetColorMetadataSize(width, height, slices, 1024, 512, total_size);
const uint64_t blocks = uint64_t {ShiftCeil(width, 10)} * ShiftCeil(height, 9);
if (levels != 1 || blocks == 0 || slices == 0 || blocks > UINT32_MAX / 4096u / slices) {
return false;
}
total_size = {static_cast<uint32_t>(blocks * slices * 4096u), 4096};
return true;
}
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
+3 -1
View File
@@ -123,9 +123,11 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t num_fragments_log2 = 0);
// mips, when given, receives the size and offset within a slice of each level that has metadata.
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0);
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0,
TileSizeOffset* mips = nullptr);
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
TileSizeAlign& total_size);
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
+38 -17
View File
@@ -1043,34 +1043,51 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
desc.info.metadata.kind != ImageMetadataKind::Cmask) {
return;
}
const auto range = desc.info.metadata.range;
const auto range = desc.info.metadata.range;
const auto& view = desc.view_info;
{
std::scoped_lock lock {m_lock};
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
// Native color metadata must not retain a reused HTile clear flag.
m_surface_metas.erase(range.address);
if (range.size == 0 || desc.info.resources.levels != 1) {
if (range.size == 0 || view.level_count != 1) {
return;
}
}
const auto layers = desc.info.TransferLayers();
// These one-mip surfaces use complete 4 KiB color metadata blocks.
// Color metadata slices consist of complete 4 KiB blocks.
constexpr uint64_t MetadataBlockSize = 0x1000;
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n");
}
const auto& view = desc.view_info;
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : metadata_base_layer;
const auto image_first = volume_texture ? 0u : view.base_layer;
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : metadata_base_layer;
const auto image_first = volume_texture ? 0u : view.base_layer;
const auto count =
volume_texture ? std::max(desc.info.extent.depth >> view.base_level, 1u) : view.layer_count;
if (first >= layers || count > layers - first) {
EXIT("TextureCache: color view exceeds its native metadata slices\n");
}
const auto slice_size = range.size / layers;
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
const auto slice_size = range.size / layers;
// Each slice holds the viewed mip's metadata in one run; DCC mips past the first tail mip
// have none. FindImage moves base_level only for one-mip descriptors, which keep the slice.
TileSizeOffset mip {.size = static_cast<uint32_t>(slice_size)};
if (desc.info.metadata.kind == ImageMetadataKind::Dcc && desc.info.resources.levels > 1) {
std::array<TileSizeOffset, 16> mips {};
TileSizeAlign total {};
(void)TileGetDccSize(desc.info.extent.width, desc.info.extent.height, layers,
desc.info.bytes_per_block, desc.info.resources.levels,
desc.info.tile_mode, total, 0, mips.data());
mip = mips[view.base_level];
}
if (mip.size == 0) {
return;
}
const GuestRange viewed {range.address + slice_size * first + mip.offset,
slice_size * (count - 1) + mip.size};
const auto run = [&](uint32_t slice) { return viewed.address + slice_size * slice; };
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
uint32_t keys = 0;
@@ -1082,7 +1099,11 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
}
}
// No selected key to clear, or none left since the last check.
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
bool checked = true;
for (uint32_t slice = 0; slice < count && checked; slice++) {
checked = m_buffer_cache.IsColorMetadataChecked(run(slice), mip.size, keys);
}
if (checked) {
return;
}
{
@@ -1107,9 +1128,9 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
const auto [buffer, offset] =
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
const auto predicates = m_tiler.CheckColorClear(
{buffer->Handle(), offset, viewed.size}, count, keys, expand);
if (expand) {
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys);
{buffer->Handle(), offset, viewed.size}, slice_size, count, keys, expand);
for (uint32_t slice = 0; slice < count && expand; slice++) {
m_buffer_cache.MarkColorMetadataChecked(run(slice), mip.size, keys);
}
const auto command = m_scheduler.Current().Handle();
const auto area = BeginColorRendering(image, view.format, view.base_level,
@@ -1142,7 +1163,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
}
for (uint32_t slice = 0; slice < count; slice++) {
const auto address = range.address + slice_size * (first + slice);
const auto address = run(slice);
uint8_t code = 0;
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
EXIT("TextureCache: failed to read color metadata backing\n");
@@ -1151,7 +1172,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
if (!DecodeColorClear(desc, code, clear.color)) {
continue;
}
std::vector<uint8_t> bytes(slice_size);
std::vector<uint8_t> bytes(mip.size);
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
EXIT("TextureCache: failed to read color metadata slice\n");
}
@@ -1175,7 +1196,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
// to overlapping image data. Invalidate the buffer before updating its backing.
if (desc.type != BindingType::VideoOut) {
std::fill(bytes.begin(), bytes.end(), uint8_t {0xff});
m_buffer_cache.InvalidateMemory(address, slice_size);
m_buffer_cache.InvalidateMemory(address, bytes.size());
LibKernel::Memory::WriteBacking(address, bytes.data(), bytes.size());
}
}
@@ -630,19 +630,20 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
return result;
}
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
bool expand) {
vk::Buffer TileManager::CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
uint32_t keys, bool expand) {
struct Check {
uint32_t metadata_base;
uint32_t words;
uint32_t stride;
uint32_t keys;
uint32_t expand;
};
static_assert(sizeof(Check) <= sizeof(Conversion));
const uint32_t max_slices =
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 ||
metadata.size / 4 > UINT32_MAX);
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % 4 != 0 || stride % 4 != 0 ||
metadata.size / 4 > UINT32_MAX || stride * (slices - 1) >= metadata.size);
if (m_color_clear == nullptr) {
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
m_clear_predicates = std::make_unique<Buffer>(
@@ -673,8 +674,9 @@ vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
static_cast<uint32_t>(writes.size()), writes.data());
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys,
expand};
const Check push {source.base,
static_cast<uint32_t>((metadata.size - stride * (slices - 1)) / 4),
static_cast<uint32_t>(stride / 4), keys, expand};
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
&push);
command.dispatch(slices, 1, 1);
+5 -5
View File
@@ -56,11 +56,11 @@ public:
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
static constexpr uint32_t ColorClearSlots = 8;
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when
// `expand` is set.
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
bool expand);
// metadata spans `slices` equal runs `stride` bytes apart. Returns the buffer whose dword
// slice * ColorClearSlots + (key >> 5) is set when every byte of that run equals a key whose
// bit `keys` selects; a hit expands to 0xff when `expand` is set.
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
uint32_t keys, bool expand);
private:
friend struct TileManagerTestAccess;
@@ -934,43 +934,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
(pipeline_bind_point == vk::PipelineBindPoint::eCompute &&
shader_stage != vk::ShaderStageFlagBits::eCompute));
}
for (const auto* reader: prepared_bindings) {
const auto& reads = reader->runtime->resources->specialization_reads;
if (reads.empty()) continue;
for (const auto* writer: prepared_bindings) {
if (writer->runtime->module->info.has_address_writes) {
EXIT("scalar resource reads cannot be proven disjoint from shader address writes\n");
}
}
for (const auto [address, size]: reads) {
for (const auto id: m_bound_images) {
const auto& image = m_context.GetTextureCache().m_slot_images[id];
if (!image.binding.shader_write && !image.binding.is_target) continue;
for (const auto written: {image.info.data, image.info.stencil,
image.info.metadata.range}) {
if (written.size != 0 && ImageRangeOverlaps(address, size,
written.address, written.size)) {
EXIT("scalar resource reads overlap an image or attachment write\n");
}
}
}
for (const auto* writer: prepared_bindings) {
const auto& program = writer->runtime->module->info;
for (const auto& resource: program.info.buffers) {
if (!resource.written || resource.descriptor_index == UINT32_MAX) continue;
const auto& written = writer->buffer_sources[resource.descriptor_index];
// ADD_TID and swizzle move fixed-offset writes past write_extent.
const bool linear = (resource.packed_stride & ((1u << 14u) | (1u << 20u))) == 0u;
const auto written_size =
linear ? std::min<uint64_t>(written.size, resource.write_extent) : written.size;
if (written_size != 0 && ImageRangeOverlaps(address, size,
written.address, written_size)) {
EXIT("scalar resource reads overlap a shader buffer write\n");
}
}
}
}
}
if (pipeline.uses_image_heap) {
m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout);
}
@@ -9,6 +9,7 @@ layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[];
layout(push_constant, std430) uniform Check {
uint metadata_base;
uint words;
uint stride;
uint keys;
uint expand;
} params;
@@ -20,7 +21,7 @@ shared uint mismatch;
void main() {
const uint slice = gl_WorkGroupID.x;
const uint base = (params.metadata_base >> 2u) + slice * params.words;
const uint base = (params.metadata_base >> 2u) + slice * params.stride;
// Each invocation reads, and may rewrite, only its own words.
if (gl_LocalInvocationIndex == 0u) {
key = metadata.data[base] & 0xffu;
@@ -859,6 +859,10 @@ uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMax, EmitUMax32>(ctx, inst);
}
uint32_t EmitSubgroupOrU32(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitSubgroupReduceU32<spv::OpGroupNonUniformBitwiseOr, EmitOrU32>(ctx, inst);
}
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto hit = state.builder.AllocateId();
@@ -173,7 +173,7 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
state.builder.AddFunction(spv::OpLoad, scalar_type, raw, pointer);
}
if (kind == VertexInputScalarKind::Uint) {
if (kind == Format::ComponentType::Uint) {
return raw;
}
@@ -236,6 +236,7 @@ uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& i
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupOrU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -273,8 +273,6 @@ struct ValueEmitContext {
uint32_t half = 0;
};
enum class VertexInputScalarKind { Float, Sint, Uint };
constexpr uint32_t NoImageComponent = 0xffffffffu;
struct DppTargetLane {
@@ -304,12 +302,11 @@ bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr);
bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr);
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location);
Format::ComponentType VertexParameterScalarKind(const EmitterState& state, uint32_t location);
uint32_t VertexParameterComponentCount(const InputBinding& input);
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind);
uint32_t VertexParameterScalarType(EmitterState& state, Format::ComponentType kind);
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
uint32_t MrtOutputType(EmitterState& state, uint32_t index);
@@ -402,47 +402,23 @@ uint32_t GlslStd450(EmitterState& state) {
return state.builder.Import("GLSL.std.450");
}
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
Format::ComponentType VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
if ((state.program.stage != ShaderType::Vertex && state.program.stage != ShaderType::Local) ||
location >= ShaderVertexInputInfo::RES_MAX ||
location >= static_cast<uint32_t>(state.input_info.vertex->resources_num)) {
return VertexInputScalarKind::Float;
}
switch (state.input_info.vertex->resources[location].Format()) {
case Prospero::BufferFormat::k8UInt:
case Prospero::BufferFormat::k16UInt:
case Prospero::BufferFormat::k8_8UInt:
case Prospero::BufferFormat::k32UInt:
case Prospero::BufferFormat::k16_16UInt:
case Prospero::BufferFormat::k8_8_8_8UInt:
case Prospero::BufferFormat::k32_32UInt:
case Prospero::BufferFormat::k16_16_16_16UInt:
case Prospero::BufferFormat::k32_32_32UInt:
case Prospero::BufferFormat::k32_32_32_32UInt: return VertexInputScalarKind::Uint;
case Prospero::BufferFormat::k8SInt:
case Prospero::BufferFormat::k16SInt:
case Prospero::BufferFormat::k8_8SInt:
case Prospero::BufferFormat::k32SInt:
case Prospero::BufferFormat::k16_16SInt:
case Prospero::BufferFormat::k8_8_8_8SInt:
case Prospero::BufferFormat::k32_32SInt:
case Prospero::BufferFormat::k16_16_16_16SInt:
case Prospero::BufferFormat::k32_32_32SInt:
case Prospero::BufferFormat::k32_32_32_32SInt: return VertexInputScalarKind::Sint;
default: return VertexInputScalarKind::Float;
return Format::ComponentType::Float;
}
return Format::GetFormatComponentType(state.input_info.vertex->resources[location].Format());
}
uint32_t VertexParameterComponentCount(const InputBinding& input) {
return std::clamp(input.component_count, 1u, 4u);
}
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind) {
uint32_t VertexParameterScalarType(EmitterState& state, Format::ComponentType kind) {
switch (kind) {
case VertexInputScalarKind::Sint: return TypeI32(state);
case VertexInputScalarKind::Uint: return TypeU32(state);
case VertexInputScalarKind::Float:
case Format::ComponentType::Sint: return TypeI32(state);
case Format::ComponentType::Uint: return TypeU32(state);
default: return TypeF32(state);
}
}
@@ -235,7 +235,8 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
if (ctx.half != 0 &&
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
op == Op::SubgroupUMaxU32 || op == Op::SubgroupOrU32 || op == Op::ReadConstBuffer ||
op == Op::ReadRealtimeClock ||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
ctx.Define(inst, ctx.other_half->Result(inst));
@@ -148,7 +148,7 @@ template <class S, class T> void Io(S& io, T& value) {
IoAll(io, __VA_ARGS__); \
}
KYTY_IO_FIELDS(MemoryInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y)
KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
KYTY_IO_FIELDS(ImageResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w)
KYTY_IO_FIELDS(SamplerResource, a, b, c, d, e, f, g, h, i, j, k)
KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
@@ -122,7 +122,6 @@ struct BufferResource {
uint32_t descriptor_index = UINT32_MAX;
uint32_t first_use_pc = 0;
uint32_t max_byte_extent = 0;
uint32_t write_extent = 0; // UINT32_MAX once a write is runtime-addressed or formatted
uint32_t packed_stride = 0;
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
@@ -224,6 +224,7 @@ VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
VALUE_OPCODE(ReadLane, U32, U32, U32)
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
VALUE_OPCODE(SubgroupOrU32, U32, U32)
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
@@ -1,7 +1,7 @@
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
#include <algorithm>
#include <optional>
#include <queue>
#include <unordered_map>
@@ -186,6 +186,16 @@ private:
ValueOpcode operation;
};
// The subgroup reduction that replaces a proven whole-wave scan of operation.
std::optional<ValueOpcode> WaveReduction(ValueOpcode operation) {
switch (operation) {
case ValueOpcode::UMin32: return ValueOpcode::SubgroupUMinU32;
case ValueOpcode::UMax32: return ValueOpcode::SubgroupUMaxU32;
case ValueOpcode::BitwiseOr32: return ValueOpcode::SubgroupOrU32;
default: return std::nullopt;
}
}
} // namespace
ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
@@ -197,15 +207,12 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
for (auto* block: program.blocks) {
for (auto& inst: *block) {
const auto opcode = inst.GetOpcode();
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
if (const auto reduce = WaveReduction(opcode)) {
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
if (!seed.IsEmpty()) {
const auto position = std::find_if(block->begin(), block->end(),
[&](const Inst& candidate) { return &candidate == &inst; });
const auto reduce = opcode == ValueOpcode::UMin32
? ValueOpcode::SubgroupUMinU32
: ValueOpcode::SubgroupUMaxU32;
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
auto& reduction = *block->PrependNewInst(position, *reduce, {seed});
inst.ReplaceUsesWith(Value(&reduction));
stats.rewritten_reductions++;
}
@@ -1167,7 +1167,7 @@ bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) {
template <typename Predicate>
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, const Predicate& predicate) {
// The renderer checks captured scalar reads against final buffer and image write ranges.
// Address writes can change the values control flow reads.
if (program.has_address_writes) {
return {};
}
@@ -4022,10 +4022,10 @@ private:
}
}
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, const Inst& inst, uint32_t pc) {
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
if (m_info.buffers[i].source == source) {
Merge(m_info.buffers[i], memory, inst, pc);
Merge(m_info.buffers[i], memory, op, pc);
return i;
}
}
@@ -4035,14 +4035,13 @@ private:
BufferResource resource;
resource.source = source;
resource.first_use_pc = pc;
Merge(resource, memory, inst, pc);
Merge(resource, memory, op, pc);
m_info.buffers.push_back(resource);
return static_cast<uint32_t>(m_info.buffers.size() - 1);
}
static void Merge(BufferResource& resource, const MemoryInfo& memory, const Inst& inst,
static void Merge(BufferResource& resource, const MemoryInfo& memory, ValueOpcode op,
uint32_t pc) {
const auto op = inst.GetOpcode();
const auto access = BufferAccessOf(op);
const bool atomic = access == BufferAccess::Atomic;
const bool write = access == BufferAccess::Write || atomic;
@@ -4050,13 +4049,6 @@ private:
resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory));
resource.read = resource.read || !write || atomic;
resource.written = resource.written || write;
if (write) {
// Formatted stores write the whole format; otherwise zero index, VGPR and SGPR offsets
// leave only the immediate offset.
const bool fixed = !memory.formatted && inst.Arg(1).Resolve() == Value(0u) &&
inst.Arg(2).Resolve() == Value(0u) && inst.Arg(3).Resolve() == Value(0u);
resource.write_extent = std::max(resource.write_extent, fixed ? ByteExtent(memory) : UINT32_MAX);
}
resource.atomic = resource.atomic || atomic;
resource.formatted = resource.formatted || memory.formatted;
resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer ||
@@ -4226,7 +4218,7 @@ private:
m_info.uses_dma = true;
return;
}
resource = AddBuffer(source, memory, inst, flags.pc);
resource = AddBuffer(source, memory, op, flags.pc);
if (resource == UINT32_MAX) {
Fail(flags.pc, "buffer resource limit exceeded");
}
@@ -524,7 +524,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
case ValueOpcode::LiveMask: info.subgroup_ballot = info.helper_invocation = true; break;
case ValueOpcode::SubgroupUMinU32:
case ValueOpcode::SubgroupUMaxU32: info.subgroup_arithmetic = true; break;
case ValueOpcode::SubgroupUMaxU32:
case ValueOpcode::SubgroupOrU32: info.subgroup_arithmetic = true; break;
case ValueOpcode::DppMoveU32:
case ValueOpcode::ReadFirstLane:
case ValueOpcode::ReadLane: {
+22 -2
View File
@@ -1,5 +1,6 @@
#include "graphics/shader/shader.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
@@ -19,6 +20,7 @@
#include <algorithm>
#include <bit>
#include <cstdio>
#include <cstring>
#include <memory>
#include <mutex>
#include <span>
@@ -59,15 +61,33 @@ void ShaderInit() {
g_shader_map = std::make_unique<std::unordered_map<uint64_t, ShaderMapEntry>>();
}
// The program's code and, at align16(code size), its literal buffer, as sized by the 0x30-byte
// footer that ends a binary; the whole binary when the footer is unknown.
static uint32_t ShaderProgramSize(const uint32_t* binary, uint32_t size) {
constexpr uint32_t FooterSize = 0x30;
if (size < FooterSize) return size;
// Magic, then version, code size and literal buffer size at +0x10.
const auto* footer = binary + (size - FooterSize) / 4;
const auto end = Common::AlignUp(uint64_t {footer[5]}, 16) + footer[6];
if (std::memcmp(footer, "barefoot", 8) != 0 || footer[4] != 1 || footer[5] == 0 ||
end % 4 != 0 || end > size - FooterSize) {
return size;
}
return static_cast<uint32_t>(end);
}
void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data) {
EXIT_IF(g_shader_map == nullptr);
if (addr == 0 || data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) {
EXIT("ShaderMapUserData shader=0x%016" PRIx64 " has invalid AGC shader_size=0x%08" PRIx32 "\n",
EXIT("ShaderMapUserData shader=0x%016" PRIx64 " has invalid shader_size=0x%08" PRIx32 "\n",
addr, data.code_size_bytes);
}
const auto hash = XXH3_64bits(reinterpret_cast<const void*>(addr), data.code_size_bytes);
const auto* binary = reinterpret_cast<const uint32_t*>(addr);
ShaderMapData mapped {data};
// Translation reads the bytes the hash covers.
mapped.code_size_bytes = ShaderProgramSize(binary, data.code_size_bytes);
const auto hash = XXH3_64bits(binary, mapped.code_size_bytes);
mapped.vertex_error =
ShaderReadVertexMetadata(data, HW::UserSgprInfo::SGPRS_MAX, mapped.vertex);
std::scoped_lock lock(g_shader_map_mutex);
+12 -9
View File
@@ -1,15 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
<linearGradient id="tile" x2="0" y2="1"><stop stop-color="#3d7fe6"/><stop offset="1" stop-color="#1c47b0"/></linearGradient>
<linearGradient id="body" x2="0" y2="1"><stop stop-color="#fff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
<linearGradient id="gloss" x2="0" y2="1"><stop stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
<rect x="16" y="16" width="480" height="480" rx="112" fill="url(#tile)"/>
<path d="M16 128 A112 112 0 0 1 128 16 L384 16 A112 112 0 0 1 496 128 L496 200 Q256 150 16 236 Z" fill="url(#gloss)"/>
<g transform="translate(256 268) scale(0.92) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
<g transform="translate(0 12)" fill-opacity="0.22" stroke="#000" stroke-opacity="0.22" stroke-width="20"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/><path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z"/></g>
<g fill="url(#body)" stroke="#0b0d12" stroke-width="20"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
<path d="M116 181 L137 121 L190 157 Z" fill="#0b0d12"/><path d="M396 181 L375 121 L322 157 Z" fill="#0b0d12"/>
<g fill="none" stroke="#0b0d12" stroke-width="20"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="20"/>
<g fill="none" stroke="#0b0d12" stroke-width="14"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
<path d="M72 316 L92 336 M70 336 L86 352 M440 316 L420 336 M442 336 L426 352" fill="none" stroke="#0b0d12" stroke-width="10"/>
<path d="M179 230 Q194 210 209 230" fill="none" stroke="#0b0d12" stroke-width="16"/>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)" stroke="#0b0d12" stroke-width="20"/>
<g fill="none" stroke="#0b0d12"><g stroke-width="14"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
<path d="M72 316 L92 336 M70 336 L86 352 M440 316 L420 336 M442 336 L426 352" stroke-width="10"/>
<path d="M179 230 Q194 210 209 230" stroke-width="16"/></g>
<circle cx="318" cy="222" r="21" fill="#0b0d12"/>
<path d="M240 256 L272 256 L256 278 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
</g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+12 -9
View File
@@ -1,15 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
<linearGradient id="tile" x2="0" y2="1"><stop stop-color="#3d7fe6"/><stop offset="1" stop-color="#1c47b0"/></linearGradient>
<linearGradient id="body" x2="0" y2="1"><stop stop-color="#fff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
<linearGradient id="gloss" x2="0" y2="1"><stop stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
<rect x="16" y="16" width="480" height="480" rx="112" fill="url(#tile)"/>
<path d="M16 128 A112 112 0 0 1 128 16 L384 16 A112 112 0 0 1 496 128 L496 200 Q256 150 16 236 Z" fill="url(#gloss)"/>
<g transform="translate(256 268) scale(0.86) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
<g transform="translate(0 18)" fill-opacity="0.22" stroke="#000" stroke-opacity="0.22" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/><path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z"/></g>
<g fill="url(#body)" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
<path d="M128 164 L137 128 L174 156 Z" fill="#0b0d12"/><path d="M384 164 L375 128 L338 156 Z" fill="#0b0d12"/>
<g fill="none" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="16"/>
<g fill="none" stroke="#0b0d12" stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" fill="none" stroke="#0b0d12" stroke-width="7"/>
<path d="M168 230 Q194 202 220 230" fill="none" stroke="#0b0d12" stroke-width="15"/>
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)" stroke="#0b0d12" stroke-width="16"/>
<g fill="none" stroke="#0b0d12"><g stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" stroke-width="7"/>
<path d="M168 230 Q194 202 220 230" stroke-width="15"/></g>
<circle cx="322" cy="220" r="22" fill="#0b0d12"/>
<path d="M238 252 L274 252 L256 276 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
</g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+5
View File
@@ -2210,6 +2210,11 @@ int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32
: ((skip + num_samples + frame_samples - 1) / frame_samples) *
codec.superframeSize;
block->num_skip_samples = num_samples == 0 ? 0 : static_cast<uint32_t>(skip);
} else if (format->waveform_type == 0x12) {
// 16-bit PCM has fixed-size frames, so the block starts exactly at the sample.
const uint64_t frame_size = uint64_t(format->num_channels) * sizeof(int16_t);
block->data_offset = uint64_t(sample_pos) * frame_size;
block->data_size = uint64_t(num_samples) * frame_size;
} else {
return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT;
}
+5
View File
@@ -651,6 +651,11 @@ void TestWaveformBlockGeometry() {
Check(Ngs2CalcWaveformBlock(&format, 0, 0, &block) == OK && block.data_size == 0 &&
block.num_samples == 0,
"empty ATRAC9 block acquired encoded data");
const Ngs2WaveformFormat pcm {0x12, 2, 48000};
Check(Ngs2CalcWaveformBlock(&pcm, 4096, 4096, &block) == OK &&
block.data_offset == 16384 && block.data_size == 16384 &&
block.num_skip_samples == 0 && block.num_samples == 4096,
"PCM block calculation omitted the sample frames");
Fixture f(24000);
const int16_t silence = 0;
f.Queue(&silence, 0, 0, UINT32_MAX);
+13 -8
View File
@@ -695,7 +695,7 @@ void TestCompleteWaveReduction() {
Shift, FetchInactive, BoundControl, Dpp8, MoveExec, UpdateExec,
Previous, DppSource, CrossExec, CrossRow, CrossSelectorLow,
CrossSelectorHigh, CrossFetch, CrossBound, CrossSource, FinalExec,
Endpoint, EndpointSource, CrossBlock, WrongOperation
Endpoint, EndpointSource, CrossBlock, WrongOperation, Or
};
for (const auto test : {Case::Masked, Case::ScalarExec, Case::Wave32,
Case::Wave32Rows, Case::Wave32Maximum, Case::RowMask, Case::BankMask,
@@ -704,7 +704,7 @@ void TestCompleteWaveReduction() {
Case::CrossExec, Case::CrossRow, Case::CrossSelectorLow,
Case::CrossSelectorHigh, Case::CrossFetch, Case::CrossBound,
Case::CrossSource, Case::FinalExec, Case::Endpoint, Case::EndpointSource,
Case::CrossBlock, Case::WrongOperation}) {
Case::CrossBlock, Case::WrongOperation, Case::Or}) {
Fixture fixture(test == Case::CrossBlock ? 2 : 1);
const auto source = fixture.Emit(ValueOpcode::LaneId);
const auto mask = fixture.Emit(ValueOpcode::ULessThan32, {source, Value(19u)});
@@ -720,14 +720,20 @@ void TestCompleteWaveReduction() {
enabled = fixture.Emit(ValueOpcode::INotEqual32,
{fixture.Emit(ValueOpcode::BitwiseAnd32, {shifted, Value(1u)}), Value(0u)});
}
// A UMax scan seeds lanes outside EXEC with zero.
// UMax and OR scans seed lanes outside EXEC with zero.
const bool maximum = test == Case::Wave32Maximum;
const uint32_t neutral = maximum ? 0u : UINT32_MAX;
const bool bitwise_or = test == Case::Or;
const uint32_t neutral = maximum || bitwise_or ? 0u : UINT32_MAX;
const auto seed = fixture.Emit(ValueOpcode::SelectU32, {mask, source, Value(neutral)});
auto prefix = fixture.Emit(ValueOpcode::SelectU32, {enabled, seed, source});
Value preserved;
auto operation = maximum ? ValueOpcode::UMax32 : ValueOpcode::UMin32;
if (test == Case::WrongOperation) operation = ValueOpcode::BitwiseOr32;
auto reduction_opcode = maximum ? ValueOpcode::SubgroupUMaxU32 : ValueOpcode::SubgroupUMinU32;
if (bitwise_or) {
operation = ValueOpcode::BitwiseOr32;
reduction_opcode = ValueOpcode::SubgroupOrU32;
}
if (test == Case::WrongOperation) operation = ValueOpcode::IAdd32;
for (const uint32_t shift : {1u, 2u, 4u, 8u}) {
DppMoveFlags flags{.control = 0x110u + shift};
if (shift == 2) {
@@ -787,13 +793,12 @@ void TestCompleteWaveReduction() {
const uint32_t wave_size = test == Case::Wave32 || rows ? 32u : 64u;
ConstantPropagationPass(fixture.program.blocks, wave_size);
const auto stats = EliminateReadLane(fixture.program, wave_size);
const bool matches = test == Case::Masked || rows;
const bool matches = test == Case::Masked || rows || bitwise_or;
Check(stats.rewritten_reductions == (matches ? 1u : 0u) && stats.rewritten_reads == 0,
("wave reduction proof failed case " + std::to_string(static_cast<unsigned>(test))).c_str());
if (matches) {
const auto *reduction = use.Instruction()->Arg(0).ResolveInstruction();
Check(reduction->GetOpcode() ==
(maximum ? ValueOpcode::SubgroupUMaxU32 : ValueOpcode::SubgroupUMinU32) &&
Check(reduction->GetOpcode() == reduction_opcode &&
reduction->Arg(0).Resolve() == seed.Resolve(),
"wave reduction changed the neutral-masked input");
RemoveIdentities(fixture.program);
+137 -38
View File
@@ -2331,6 +2331,10 @@ public:
vertex.shader_data_buffer.offset == 0,
"repeated shader binding preparation allocated or retained stale state");
// Scalar resource reads are captured before the draw, so a stage that writes through
// computed addresses commits alongside them.
vertex_module.info.has_address_writes = true;
pixel_snapshot.specialization_reads = {{0x1000u, 4u}};
const auto pipeline = RenderExecutorTestAccess::CommitBindings(
context.GetRenderExecutor(), scheduler.Current(), vertex, pixel);
Require(name, "shared push data",
@@ -12712,6 +12716,15 @@ public:
DecodeColorDwordFill(vk::Format::eR16G16B16A16Uint, 0x22221111u, clear) &&
clear.uint32[2] == 0x1111u && clear.uint32[3] == 0x2222u,
"a DWORD fill did not repeat across the 64-bit pixel");
std::array<TileSizeOffset, 16> chain{};
TileSizeAlign chain_size{};
Require(name, "DCC mip chain layout",
TileGetDccSize(512, 256, 2, 8, 5, Prospero::TileMode::kRenderTarget, chain_size, 0,
chain.data()) &&
chain_size.size == 0x8000 && chain[0].offset == 0x3000 &&
chain[1].offset == 0x2000 && chain[2].offset == 0x1000 && chain[3].offset == 0 &&
chain[3].size == 0x1000 && chain[4].size == 0,
"a DCC slice did not store its mips backwards after the first tail mip's block");
constexpr uintptr_t base = 0x0000000204100000ull;
constexpr uint64_t allocation_size = 0x1000000;
constexpr uint64_t allocation_alignment = 0x10000;
@@ -12727,6 +12740,7 @@ public:
bool eliminate_fast_clear = false;
uint32_t samples_log2 = 0;
bool goat = false;
bool dcc_mips = false;
};
constexpr std::array cases{
FillCase{0x40404040u, {0, 0x3c000000u}},
@@ -12767,6 +12781,8 @@ public:
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
.eliminate_fast_clear = true, .samples_log2 = 2},
FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .eliminate_fast_clear = true},
FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .reuse_mips = true,
.dcc_mips = true},
};
EnsureRuntimeContext();
// Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted.
@@ -12832,13 +12848,19 @@ public:
const auto bytes_per_pixel = TextureGetRenderTargetFormat(
layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element;
const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1;
const uint32_t selected_layer = fill_case.cmask || fill_case.eliminate_fast_clear ? 1 : 0;
const uint32_t selected_layer =
fill_case.cmask || fill_case.eliminate_fast_clear || fill_case.dcc_mips ? 1 : 0;
TileSizeAlign dcc_size{};
(void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel, 1,
Prospero::TileMode::kRenderTarget, dcc_size, fill_case.samples_log2);
const uint64_t dcc_address = base + (fill_case.samples_log2 != 0 ? 0x800000 :
fill_case.eliminate_fast_clear ? 0x200000 :
fill_case.reuse_mips ? 0x160000 : 0x100000);
(void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel,
fill_case.dcc_mips ? 2 : 1, Prospero::TileMode::kRenderTarget, dcc_size,
fill_case.samples_log2);
uint64_t dcc_offset = fill_case.reuse_mips ? 0x300000 : 0x100000;
if (fill_case.samples_log2 != 0) {
dcc_offset = 0x800000;
} else if (fill_case.eliminate_fast_clear) {
dcc_offset = 0x200000;
}
const uint64_t dcc_address = base + dcc_offset;
const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size;
const std::vector<u32> expected(
fill_case.texel.begin(), fill_case.texel.begin() + std::max(bytes_per_pixel / 4u, 1u));
@@ -12862,7 +12884,8 @@ public:
.last_array_slice_index = selected_layer});
registers.SetColorAttrib(0, {.num_samples = fill_case.samples_log2,
.num_fragments = fill_case.samples_log2});
registers.SetColorAttrib2(0, {.height = 255, .width = 511});
registers.SetColorAttrib2(
0, {.height = 255, .width = 511, .num_mip_levels = fill_case.dcc_mips ? 1u : 0u});
registers.SetColorAttrib3(0,
{.tile_mode = Prospero::TileMode::kRenderTarget,
.dimension = 1,
@@ -12909,7 +12932,7 @@ public:
clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 0.0f};
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), preexisting_id,
{vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear);
{vk::ImageAspectFlagBits::eColor, 0, 2, 0, selected_layer + 1}, clear);
}
if (fill_case.eliminate_fast_clear) {
vk::ClearValue stale{};
@@ -12963,7 +12986,10 @@ public:
Require(name, "expanded metadata readback",
LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()),
"native color metadata could not be read back");
const auto selected = bytes.begin() + metadata_size * selected_layer / (selected_layer + 1);
// Mip 1's metadata block precedes mip 0's in each slice.
const auto selected = bytes.begin() +
metadata_size * selected_layer / (selected_layer + 1) +
(fill_case.dcc_mips ? 0x1000 : 0);
Require(name, "selected metadata slice expansion",
std::all_of(bytes.begin(), selected,
[&](uint8_t v) { return v == static_cast<uint8_t>(fill_case.fill); }) &&
@@ -13055,12 +13081,12 @@ public:
const auto &image = texture_cache.GetImage(color.image_id);
Require(name, "single-mip clear reuses mip chain",
color.image_id == preexisting_id && image.info.resources.levels == 2 &&
color.desc.info.resources.levels == 1 &&
color.desc.info.resources.levels == (fill_case.dcc_mips ? 2u : 1u) &&
color.desc.view_info.base_level == 0 && color.desc.view_info.level_count == 1,
"the single-mip target did not retain its cached image and view scope");
Require(name, "single-mip clear preserves higher mip",
ReadCachedTexel(name, context, color.image_id, {255, 127, 0},
{1, 1, 1}, 0, 1) ==
{1, 1, 1}, selected_layer, 1) ==
std::vector<u32>{0x00003c00u, 0x00003c00u},
"clearing mip zero changed its higher mip");
}
@@ -13269,6 +13295,58 @@ public:
Require(name, "native color clear after HTile reuse", read_texel() == expected,
"the former HTile entry swallowed the native color metadata fill");
check_expanded_metadata();
if (fill_case.dcc_mips) {
// A layered view checks and expands each slice's mip-0 run, one slice apart.
registers.SetColorView(0, {.base_array_slice_index = 0, .last_array_slice_index = 1});
RenderExecutorTestAccess::ResetBindings(executor);
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), color, 0);
fill_metadata(metadata_words);
bind();
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
std::vector<uint8_t> bytes(metadata_size);
bool runs = LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size());
for (uint64_t i = 0; i < bytes.size(); i++) {
runs &= bytes[i] == ((i & 0x1000) != 0 ? 0xff : static_cast<uint8_t>(fill_case.fill));
}
Require(name, "layered mip-0 clear",
runs && read_texel() == expected &&
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 0) == expected,
"a layered mip view missed a slice or expanded another mip's metadata");
// A one-mip target at mip 1's address reads its own metadata and clears that mip.
const auto chain_id = color.image_id;
const uint64_t mip_dcc = dcc_address + 0x10000;
registers.SetColorAttrib2(0, {.height = 127, .width = 255});
registers.SetColorView(0, {});
registers.SetColorDccAddr(0, {.addr = mip_dcc});
WriteMetadata(context, mip_dcc, 0x1000, fill_case.fill);
RenderExecutorTestAccess::ResetBindings(executor);
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), color, 0);
Require(name, "one-mip clear of a cached mip",
color.image_id == chain_id && color.desc.view_info.base_level == 1 &&
ReadCachedTexel(name, context, chain_id, {}, {1, 1, 1}, 0, 1) == expected,
"a one-mip target matched to a cached mip skipped its metadata clear");
// A volume's mip-1 view clears that mip's depth slices from their first tail blocks.
constexpr uint64_t volume_dcc = base + 0x320000;
auto volume = color.desc;
volume.type = BindingType::Texture;
volume.info.data = {base + 0x400000, 0};
volume.info.type = Prospero::ImageType::kColor3D;
volume.info.extent = {128, 64, 4};
volume.info.resources = {2, 1};
volume.info.UpdateSize();
volume.info.metadata.range = {volume_dcc, 0x8000};
volume.view_info.type = vk::ImageViewType::e3D;
volume.view_info.base_level = 1;
volume.view_info.usage = vk::ImageUsageFlagBits::eSampled;
WriteMetadata(context, volume_dcc, 0x8000, fill_case.fill);
const auto volume_id = texture_cache.FindImage(volume);
Require(name, "volume mip clear",
ReadCachedTexel(name, context, volume_id, {0, 0, 1}, {1, 1, 1}, 0, 1) ==
expected,
"a volume mip view missed its depth slices' clear");
}
RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size);
scheduler.Finish();
@@ -45617,60 +45695,75 @@ TestCase ImageAtomicGlc0DoesNotReturnOldValue() {
return test;
}
void AppendWave64Minimum(std::vector<u32> &code) {
// Reduces v13 into v0 with V_MIN_U32/S_MIN_U32 or, for bitwise_or, V_OR_B32/S_OR_B32.
void AppendWave64Reduction(std::vector<u32> &code, bool bitwise_or) {
const u32 vector_op = bitwise_or ? 0x1cu : 0x13u;
code.insert(code.end(), {EncodeSop1(0x04, 24, 126), // Save contributing lanes.
EncodeSop1(0x28, 106, 126)});
AppendVop3(&code, 0x101, 13, 193, Vgpr(13), 24); // Inactive lanes contribute UINT_MAX.
// Inactive lanes contribute the identity: 0 for OR, UINT_MAX for the minimum.
AppendVop3(&code, 0x101, 13, bitwise_or ? 128u : 193u, Vgpr(13), 24);
for (u32 shift : {1u, 2u, 4u, 8u}) {
code.push_back(EncodeVop2(0x13, 13, 250, 13));
code.push_back(EncodeVop2(vector_op, 13, 250, 13));
code.push_back(EncodeVop2Dpp(13, 0x110u + shift));
}
// Exact RDNA2 wave64 reduction from PS 964747887d898821, PCs 0x224..0x244.
// RDNA2 wave64 reduction from PS 964747887d898821, PCs 0x224..0x244.
code.insert(code.end(), {0xd778100cu, 0x0305830du,
EncodeVop2(0x13, 13, Vgpr(13), 12), EncodeSop1(0x04, 126, 106),
EncodeVop2(vector_op, 13, Vgpr(13), 12), EncodeSop1(0x04, 126, 106),
0xd7600006u, 0x00013f0du, 0xd7600007u, 0x00017f0du,
EncodeSop2(0x07, 8, 6, 7), EncodeVop1(0x01, 0, 8)});
EncodeSop2(bitwise_or ? 0x10u : 0x07u, 8, 6, 7), EncodeVop1(0x01, 0, 8)});
}
GraphicsCase GraphicsSparseWaveMinimum() {
std::vector<ShaderOpcode> Wave64ReductionOpcodes(bool bitwise_or) {
using O = ShaderOpcode;
return {bitwise_or ? O::V_OR_B32 : O::V_MIN_U32, O::V_PERMLANEX16_B32,
O::V_READLANE_B32, bitwise_or ? O::S_OR_B32 : O::S_MIN_U32};
}
GraphicsCase GraphicsSparseWaveReduction(bool bitwise_or) {
GraphicsCase test;
test.name = "GraphicsSparseWaveMinimum";
test.name = bitwise_or ? "GraphicsSparseWaveOr" : "GraphicsSparseWaveMinimum";
auto &code = test.fragment_code;
code = {EncodeVintrp(0x02, 13, 0, 0, 2)};
AppendWave64Minimum(code);
AppendWave64Reduction(code, bitwise_or);
code.insert(code.end(), {EncodeExp0(0x00, 0xf), EncodeExp1(0, 0, 0, 0)});
AppendEnd(&code);
test.pixel_interpolator_settings = {0x00000400u};
test.expected_pixel = {0x3e800000u, 0x3e800000u, 0x3e800000u, 0x3e800000u};
test.opcodes = {ShaderOpcode::V_MIN_U32, ShaderOpcode::V_PERMLANEX16_B32,
ShaderOpcode::V_READLANE_B32, ShaderOpcode::S_MIN_U32};
test.opcodes = Wave64ReductionOpcodes(bitwise_or);
return test;
}
TestCase ComputeWave64Minimum(bool masked) {
TestCase ComputeWave64Reduction(bool masked, bool bitwise_or) {
TestCase test;
test.name = masked ? "ComputeWave64MinimumMasked" : "ComputeWave64Minimum";
auto &code = test.code;
code = {EncodeVop2(0x1a, 20, InlineU32(2), 0)};
AppendVMovU32(&code, 13, 100);
code.push_back(EncodeVop2(0x26, 13, Vgpr(13), 0)); // 100 - lane: minimum is in the upper half.
if (bitwise_or) {
// Lane i contributes bit i / 4, so each half supplies its own byte of 0xffff.
test.name = "ComputeWave64Or";
test.expected.assign(64, 0xffffu);
code.push_back(EncodeVop2(0x16, 13, InlineU32(2), 0));
AppendVop3(&code, 0x11a, 13, Vgpr(13), InlineU32(1));
} else {
test.name = masked ? "ComputeWave64MinimumMasked" : "ComputeWave64Minimum";
test.expected.assign(64, masked ? 38u : 37u);
AppendVMovU32(&code, 13, 100);
code.push_back(EncodeVop2(0x26, 13, Vgpr(13), 0)); // 100 - lane: minimum is in the upper half.
}
if (masked) {
AppendSMovLiteral(&code, 126, 0xfffffffeu);
AppendSMovLiteral(&code, 127, 0x7fffffffu);
}
AppendWave64Minimum(code);
AppendWave64Reduction(code, bitwise_or);
AppendBufferStoreDword(&code, 0, 20);
AppendEnd(&code);
test.initial.assign(64, 0xa5a5a5a5u);
test.expected.assign(64, masked ? 38u : 37u);
if (masked) test.expected.front() = test.expected.back() = 0xa5a5a5a5u;
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.wave_size = 64;
test.has_compute_info = true;
test.opcodes = {ShaderOpcode::V_MIN_U32, ShaderOpcode::V_PERMLANEX16_B32,
ShaderOpcode::V_READLANE_B32, ShaderOpcode::S_MIN_U32};
test.opcodes = Wave64ReductionOpcodes(bitwise_or);
return test;
}
@@ -46549,8 +46642,9 @@ std::vector<TestCase> MakeCases() {
cases.push_back(VectorDppMirrorAndRotate(64));
cases.push_back(VectorReadFirstLaneExecMasks(32));
cases.push_back(VectorReadFirstLaneExecMasks(64));
cases.push_back(ComputeWave64Minimum(false));
cases.push_back(ComputeWave64Minimum(true));
cases.push_back(ComputeWave64Reduction(false, false));
cases.push_back(ComputeWave64Reduction(true, false));
cases.push_back(ComputeWave64Reduction(false, true));
AddCase(VectorDppBankMaskPreservesDestination);
for (const bool row_shift : {false, true})
for (const bool bound_ctrl : {false, true})
@@ -46994,7 +47088,8 @@ std::vector<TestCase> MakeCases() {
std::vector<GraphicsCase> MakeGraphicsCases() {
return {
GraphicsSparseWaveMinimum(),
GraphicsSparseWaveReduction(false),
GraphicsSparseWaveReduction(true),
GraphicsInterpolationExport(),
GraphicsPositionWExport(),
GraphicsPackedHalfCentroid(),
@@ -48040,8 +48135,10 @@ void CheckEmbeddedFetchVertexOffset() {
return ResolveDrawOffsets(0, vertex).second;
};
// Packed integer formats load integers too.
for (const auto format : {Prospero::BufferFormat::k32_32_32_32Float,
Prospero::BufferFormat::k32_32_32_32UInt}) {
Prospero::BufferFormat::k32_32_32_32UInt,
Prospero::BufferFormat::k10_10_10_2UInt}) {
const auto local = Compile("LocalEmbeddedFetch", MakeFetch({}, {}, 0, false, true),
0, ShaderType::Local, format);
ValidateSpirv("LocalEmbeddedFetch", local.spirv);
@@ -48050,7 +48147,7 @@ void CheckEmbeddedFetchVertexOffset() {
return input.kind == ShaderRecompiler::IR::StageInputKind::Parameter &&
input.location == 0;
}), "LS indexed fetch must consume native vertex attributes");
if (format == Prospero::BufferFormat::k32_32_32_32UInt) {
if (format != Prospero::BufferFormat::k32_32_32_32Float) {
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
std::string assembly;
Require("LocalEmbeddedFetch", "disassembly", tools.Disassemble(local.spirv, &assembly),
@@ -53097,9 +53194,11 @@ int main(int argc, char **argv) {
}
if (argc == 2 && std::strcmp(argv[1], "--dpp-only") == 0) {
VulkanHarness vulkan;
RunGraphicsCase(&vulkan, GraphicsSparseWaveMinimum());
RunCase(&vulkan, ComputeWave64Minimum(false));
RunCase(&vulkan, ComputeWave64Minimum(true));
RunGraphicsCase(&vulkan, GraphicsSparseWaveReduction(false));
RunGraphicsCase(&vulkan, GraphicsSparseWaveReduction(true));
RunCase(&vulkan, ComputeWave64Reduction(false, false));
RunCase(&vulkan, ComputeWave64Reduction(true, false));
RunCase(&vulkan, ComputeWave64Reduction(false, true));
RunCase(&vulkan, VectorDpp8Captured(false));
for (const u32 wave_size : {32u, 64u})
for (const bool fi : {false, true})
+37
View File
@@ -1941,6 +1941,42 @@ void TestVertexBufferGrouping() {
Check(!input.has_dynamic_vertex_fetch, "remapping a shader kept its previous vertex metadata");
}
void TestShaderProgramHash() {
using Binary = std::array<uint32_t, 22>;
// One code word, a 16-byte literal buffer at 16, 8 bytes of metadata, then the 0x30-byte
// footer, whose key and checksum cover the whole binary.
const auto Make = [](uint32_t literal, uint32_t metadata, uint32_t version = 1,
uint32_t code_size = 4) {
Binary words{};
words[0] = EncodeSopp(0x01);
words[4] = literal;
words[8] = metadata;
std::memcpy(&words[10], "barefoot", 8);
words[12] = metadata;
words[14] = version;
words[15] = code_size;
words[16] = 16;
words[18] = metadata;
return words;
};
const auto Map = [](const Binary &binary) {
ShaderMappedData mapped{};
mapped.code_size_bytes = sizeof(binary);
HW::ComputeShaderInfo regs{};
regs.cs_regs.data_addr = reinterpret_cast<uint64_t>(binary.data());
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
ShaderComputeInputInfo input{};
return PrepareProgram(regs, input);
};
Check(Map(Make(7, 1)).hash == Map(Make(7, 2)).hash,
"binaries differing only in metadata got different programs");
Check(Map(Make(7, 1)).hash != Map(Make(8, 1)).hash, "the program hash missed the literal buffer");
Check(Map(Make(7, 1)).code.size() == 8, "translation must read only the hashed program bytes");
Check(Map(Make(7, 1, 2)).hash != Map(Make(7, 2, 2)).hash &&
Map(Make(7, 1, 1, 0)).hash != Map(Make(7, 2, 1, 0)).hash,
"a binary with an unknown footer must hash whole");
}
void TestNggVertexEntryState() {
using namespace ShaderRecompiler;
// PPSA03309 shader 5a39eb2021a5d2c1: retain its NGG prologue and replace
@@ -17221,6 +17257,7 @@ int main() {
TestNewShaderRecompilerSMovB32();
TestShaderStageBarriers();
TestVertexBufferGrouping();
TestShaderProgramHash();
TestNggVertexEntryState();
TestNewShaderRecompilerClipDisabledPosition();
TestNewShaderRecompilerAuxPositionExports();