mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 04:16:57 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79837b423d | |||
| aafe014d0c | |||
| a358b847c1 | |||
| a951a91703 | |||
| a875d1b205 |
@@ -676,6 +676,12 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME wait_until_safe_for_rendering
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||
add_test(NAME depth_bits
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-bits-only)
|
||||
add_test(NAME msaa_storage
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --msaa-storage-only)
|
||||
add_test(NAME pm4_context_state
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||
add_test(NAME gpu_tiler
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -91,6 +92,12 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::Wake() {
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
m_wake_pending = true;
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::ProcessCommands() {
|
||||
EXIT_IF(!IsGpuThread());
|
||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||
@@ -464,7 +471,10 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
}
|
||||
if (selected_queue < 0) {
|
||||
gpu->m_processing = false;
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
// A wake that came before this wait must not be lost.
|
||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
}
|
||||
for (auto& queue: gpu->m_queues) {
|
||||
if (!queue.empty()) {
|
||||
queue.front().blocked = false;
|
||||
@@ -998,10 +1008,11 @@ void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
// The SDK stalls only this command processor, not the host command lane or other queues.
|
||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index))) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
||||
void Wake();
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
@@ -87,6 +89,7 @@ private:
|
||||
bool m_processing = false;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_wake_pending = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
|
||||
@@ -90,7 +90,8 @@ ImageCreateFlags(const GraphicContext& graphics, const ImageInfo& info, vk::Imag
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
|
||||
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||
}
|
||||
if (info.samples == 1) {
|
||||
if (graphics.GetPhysicalDeviceProperties().limits.storageImageSampleCounts &
|
||||
static_cast<vk::SampleCountFlagBits>(info.samples)) {
|
||||
usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
return usage;
|
||||
|
||||
@@ -101,6 +101,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
|
||||
@@ -254,6 +254,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor3D &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool is_2d_msaa =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor2DMsaa && descriptor.Depth() == 0 &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
TileTextureBlockLayout tile_layout {};
|
||||
bool supported_tile = false;
|
||||
switch (tile) {
|
||||
@@ -284,8 +288,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool supported_swizzle =
|
||||
IsValidImageSwizzle(swizzle) &&
|
||||
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
|
||||
descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d || is_2d_msaa) &&
|
||||
supported_tile && descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
|
||||
!descriptor.MsaaDepth();
|
||||
}
|
||||
@@ -356,7 +360,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
}
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping =
|
||||
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
storage || resource.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
? vk::ComponentMapping {}
|
||||
: TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage);
|
||||
switch (resource.dimension) {
|
||||
@@ -515,7 +519,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto format = descriptor.Format();
|
||||
const auto format = ShaderRecompiler::IR::ReadsDepthBits(resource)
|
||||
? Prospero::BufferFormat::k16UNorm
|
||||
: descriptor.Format();
|
||||
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
||||
const bool shader_conversion =
|
||||
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||
|
||||
private:
|
||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||
@@ -240,6 +240,7 @@ private:
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
void SignalDoneLocked();
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
Common::Mutex m_mutex;
|
||||
@@ -250,6 +251,7 @@ private:
|
||||
std::list<Request> m_cpu_requests;
|
||||
std::list<Request> m_cancelled_requests;
|
||||
bool m_processing = false;
|
||||
bool m_gpu_waiting = false;
|
||||
uint64_t m_next_request_id = 1;
|
||||
};
|
||||
|
||||
@@ -953,7 +955,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
}
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||
@@ -1033,7 +1035,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
it = queue->erase(it);
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_submit_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
@@ -1205,19 +1207,26 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto has_request = [this, &cfg, index] {
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
};
|
||||
while (has_request()) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
void FlipQueue::SignalDoneLocked() {
|
||||
m_done_cond_var.SignalAll();
|
||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||
if (m_gpu_waiting) {
|
||||
m_gpu_waiting = false;
|
||||
m_presenter.Renderer().GetGpu().Wake();
|
||||
}
|
||||
}
|
||||
|
||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
m_gpu_waiting = !safe;
|
||||
return safe;
|
||||
}
|
||||
|
||||
bool FlipQueue::Flip(uint32_t micros) {
|
||||
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
|
||||
|
||||
@@ -1311,7 +1320,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_processing = false;
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
@@ -1694,12 +1703,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
||||
auto* ctx = m_impl->Get(handle);
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot(int handle);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -609,6 +609,8 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.depthClamp = VK_TRUE;
|
||||
#endif
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderStorageImageMultisample =
|
||||
supported_features2.features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
@@ -623,6 +625,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.multiViewport = VK_TRUE;
|
||||
device_features.fillModeNonSolid = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
// Storage image sample counts beyond 1x require the feature.
|
||||
if (!device_features.shaderStorageImageMultisample) {
|
||||
graphics.physical_device_properties.limits.storageImageSampleCounts =
|
||||
vk::SampleCountFlagBits::e1;
|
||||
}
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
device_features.shaderFloat64 =
|
||||
|
||||
@@ -169,6 +169,9 @@ uint32_t Builder::AddString(const char* text) {
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
if (!m_emit_names) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(operands, name);
|
||||
AppendInstruction(m_debug, spv::OpName, operands);
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t AddString(const char* text);
|
||||
void EnableNames() { m_emit_names = true; }
|
||||
void AddName(uint32_t target, const char* name);
|
||||
template <typename... Args>
|
||||
void AddAnnotation(spv::Op opcode, const Args&... operands) {
|
||||
@@ -159,6 +160,7 @@ private:
|
||||
std::map<std::string, uint32_t> m_import_ids;
|
||||
std::map<std::vector<uint32_t>, uint32_t> m_declaration_ids;
|
||||
size_t m_unpatched_phi_incomings = 0;
|
||||
bool m_emit_names = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -226,6 +226,7 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
|
||||
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
|
||||
program.shader_hash);
|
||||
state.debug_source = state.builder.AddString(name);
|
||||
state.builder.EnableNames();
|
||||
}
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
|
||||
@@ -121,6 +121,9 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
if (sampled == 2 && info.multisampled != 0u) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageMultisample);
|
||||
}
|
||||
const auto scalar_type =
|
||||
image.atomic64 ? TypeU64(state) : ImageScalarType(state, image.numeric_class);
|
||||
return state.builder.Type(spv::OpTypeImage, scalar_type,
|
||||
@@ -213,15 +216,20 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index) {
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip, array_index),
|
||||
coord, texel);
|
||||
const auto descriptor = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
if (sample == 0u) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel);
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel,
|
||||
spv::ImageOperandsSampleMask, sample);
|
||||
}
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
EmitWrite(0);
|
||||
|
||||
@@ -389,6 +389,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
||||
const auto format = image.conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
if (IR::ReadsDepthBits(image)) return info;
|
||||
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
|
||||
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
|
||||
info.component_count > 4u);
|
||||
@@ -424,6 +425,7 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
|
||||
const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class;
|
||||
const auto scalar_type = ImageScalarType(ctx.state, numeric_class);
|
||||
const bool integer = info.packed_bitfield || info.type == Format::ComponentType::Uint;
|
||||
uint32_t packed = 0;
|
||||
if (info.packed_bitfield) {
|
||||
packed = ctx.state.builder.AllocateId();
|
||||
@@ -441,8 +443,16 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
|
||||
components[component], texel, component);
|
||||
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
|
||||
const auto unorm_max = static_cast<float>((1u << info.component_bits[component]) - 1u);
|
||||
components[component] = Binary(ctx.state, spv::OpFMul, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 255.0f));
|
||||
ConstantF32Value(ctx.state, unorm_max));
|
||||
if (integer) {
|
||||
// Depth bits: round back to the stored UNorm code.
|
||||
components[component] =
|
||||
Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
|
||||
Binary(ctx.state, spv::OpFAdd, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 0.5f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t component = info.component_count; component < 4u; component++) {
|
||||
@@ -454,20 +464,19 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
const auto selector = (swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = info.packed_bitfield
|
||||
selected[component] = integer
|
||||
? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One))
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
selected[component] = components[selector - 4u];
|
||||
} else {
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 0u)
|
||||
: ZeroF32(ctx.state);
|
||||
selected[component] = integer ? ConstantU32(ctx.state, 0u) : ZeroF32(ctx.state);
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
|
||||
selected[1], selected[2], selected[3]);
|
||||
integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4),
|
||||
result, selected[0], selected[1], selected[2], selected[3]);
|
||||
return info.type == Format::ComponentType::Unorm
|
||||
? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result;
|
||||
}
|
||||
@@ -932,9 +941,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto mip_lod = candidate.mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, mem.image_dimension) : 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, candidate.dimension);
|
||||
const auto& dimension_info = ImageDimensionInfoFor(candidate.dimension);
|
||||
const auto sample = dimension_info.multisampled != 0u
|
||||
? AddressU32(ctx, mem, *address, dimension_info.coordinate_components) : 0u;
|
||||
const auto texel = StoreTexel(ctx, selected, ctx.Arg(inst, 2),
|
||||
candidate.numeric_class == Prospero::TextureNumericClass::Uint);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index, sample);
|
||||
});
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -339,8 +339,9 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
uint32_t mip = 0, uint32_t array_index = 0,
|
||||
bool sampler_dynamic = false);
|
||||
|
||||
// sample is 0 (no id) for single-sampled images.
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index);
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample);
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage);
|
||||
|
||||
|
||||
@@ -188,6 +188,12 @@ RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format
|
||||
return Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
// A raw load of a kDepth k16UInt T# reads a D16 surface's codes. Vulkan cannot view D16 as
|
||||
// R16Uint, so the host binds it as UNorm and the shader converts back.
|
||||
[[nodiscard]] constexpr bool ReadsDepthBits(const ImageResource& image) {
|
||||
return image.conversion_format == Prospero::BufferFormat::k16UInt;
|
||||
}
|
||||
|
||||
struct SamplerResource {
|
||||
static constexpr uint32_t NoIndirectSampler = UINT32_MAX;
|
||||
|
||||
@@ -337,7 +343,7 @@ struct StageOutput {
|
||||
inline constexpr uint32_t FirstImageBinding = 1u;
|
||||
inline constexpr uint32_t FirstComparisonImageBinding = 22u;
|
||||
inline constexpr uint32_t FirstStorageImageBinding = 29u;
|
||||
inline constexpr uint32_t ImageBindingCount = 48u;
|
||||
inline constexpr uint32_t ImageBindingCount = 50u;
|
||||
|
||||
enum class DescriptorBindingKind : uint32_t {
|
||||
Buffers = 0u,
|
||||
@@ -351,8 +357,8 @@ enum class DescriptorBindingKind : uint32_t {
|
||||
Count,
|
||||
};
|
||||
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 56u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 51u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u);
|
||||
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
@@ -413,8 +419,8 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
constexpr uint32_t SampledUintBinding = 8u;
|
||||
constexpr uint32_t SampledSintBinding = 15u;
|
||||
constexpr uint32_t StorageFloatBinding = FirstStorageImageBinding;
|
||||
constexpr uint32_t StorageUintBinding = StorageFloatBinding + 5u;
|
||||
constexpr uint32_t AtomicUintBinding = StorageUintBinding + 5u;
|
||||
constexpr uint32_t StorageUintBinding = StorageFloatBinding + 6u;
|
||||
constexpr uint32_t AtomicUintBinding = StorageUintBinding + 6u;
|
||||
|
||||
uint32_t base = 0;
|
||||
bool sampled = false;
|
||||
@@ -461,10 +467,10 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
case Decoder::ImageDimension::Dim2D: dimension = 2u; break;
|
||||
case Decoder::ImageDimension::Dim2DArray: dimension = 3u; break;
|
||||
case Decoder::ImageDimension::Dim2DMsaa:
|
||||
if (!sampled) {
|
||||
if (image.atomic) {
|
||||
return std::nullopt;
|
||||
}
|
||||
dimension = 4u;
|
||||
dimension = sampled ? 4u : 5u;
|
||||
break;
|
||||
case Decoder::ImageDimension::Dim2DMsaaArray:
|
||||
if (!sampled) {
|
||||
|
||||
@@ -932,7 +932,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
return SpecializationFail("FMASK requires a direct image load");
|
||||
}
|
||||
}
|
||||
image.conversion_format = ImageConversionFormat(format);
|
||||
// See ReadsDepthBits; raw loads only, since filtering would blend the codes.
|
||||
const bool depth_bits =
|
||||
!storage && base.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt &&
|
||||
static_cast<Prospero::TileMode>((descriptor.dwords[3] >> 20u) & 0x1fu) ==
|
||||
Prospero::TileMode::kDepth;
|
||||
image.conversion_format = depth_bits ? format : ImageConversionFormat(format);
|
||||
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
@@ -940,7 +945,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
const bool raw_sint_storage = raw_sint_format != Prospero::BufferFormat::kInvalid;
|
||||
if (raw_sint_format == Prospero::BufferFormat::k16UInt) image.conversion_format = format;
|
||||
image.numeric_class = Prospero::SampledTextureNumericClass(
|
||||
Prospero::RemapTextureFormat(format));
|
||||
depth_bits ? Prospero::BufferFormat::k16UNorm : Prospero::RemapTextureFormat(format));
|
||||
if (!storage && format == Prospero::BufferFormat::k11_11_10UNorm) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage) {
|
||||
return SpecializationFail("packed UNorm sampling requires a direct image");
|
||||
|
||||
@@ -410,6 +410,11 @@ std::filesystem::path GetRealFilename(const std::string& mounted_file_name) {
|
||||
return g_mount_points->ResolvePath(mounted_file_name);
|
||||
}
|
||||
|
||||
bool IsOpenDescriptor(int d) {
|
||||
const auto* file = g_files->GetFile(d);
|
||||
return file != nullptr && file->opened;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -56,6 +56,8 @@ void Mount(const std::filesystem::path& folder, const std::stri
|
||||
void Umount(const std::string& folder_or_point);
|
||||
// Returns an empty path when the guest path has no mounted filesystem.
|
||||
std::filesystem::path GetRealFilename(const std::string& mounted_file_name);
|
||||
// Returns whether d is an open file, directory or device descriptor.
|
||||
bool IsOpenDescriptor(int d);
|
||||
|
||||
int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode);
|
||||
int KYTY_SYSV_ABI KernelClose(int d);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
@@ -2577,6 +2578,7 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
|
||||
std::vector<std::pair<int, NativeSocket>> descriptors;
|
||||
std::vector<std::shared_ptr<SocketTransport>> stream_reads;
|
||||
std::vector<std::pair<void*, int>> ready_files;
|
||||
int host_nfds = 0;
|
||||
for (int fd = 0; fd < nfds; fd++) {
|
||||
const bool read = GuestFdIsSet(readfds, fd);
|
||||
@@ -2585,6 +2587,16 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
if (!read && !write && !except) {
|
||||
continue;
|
||||
}
|
||||
// FreeBSD polls files and devices ready to read and write at once.
|
||||
if (!IsSocket(fd) && LibKernel::FileSystem::IsOpenDescriptor(fd)) {
|
||||
if (read) {
|
||||
ready_files.emplace_back(readfds, fd);
|
||||
}
|
||||
if (write) {
|
||||
ready_files.emplace_back(writefds, fd);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
std::shared_ptr<SocketTransport> transport;
|
||||
#if !defined(_WIN32)
|
||||
@@ -2636,6 +2648,10 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
host_timeout.tv_usec = static_cast<decltype(host_timeout.tv_usec)>(guest_timeout->tv_usec);
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
if (!ready_files.empty()) {
|
||||
host_timeout = {};
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
#if defined(_WIN32)
|
||||
@@ -2672,6 +2688,10 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
result++;
|
||||
}
|
||||
}
|
||||
for (const auto& [fds, fd]: ready_files) {
|
||||
GuestFdSet(fds, fd);
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -324,6 +324,7 @@ void CheckSaveRename(const std::filesystem::path &root,
|
||||
}
|
||||
|
||||
void TestRandomDevices() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
for (const auto* path : {"/dev/urandom", "/dev/random"}) {
|
||||
const int fd = FileSystem::KernelOpen(path, 0, 0);
|
||||
FileSystem::FileStat stat {};
|
||||
@@ -333,7 +334,18 @@ void TestRandomDevices() {
|
||||
std::array<uint8_t, 32> entropy {};
|
||||
Check(FileSystem::KernelRead(fd, entropy.data(), entropy.size()) == entropy.size(),
|
||||
"character device supplies requested entropy");
|
||||
// OpenSSL RAND_poll selects the device before reading it; a null timeout must not block.
|
||||
std::array<uint64_t, 16> readable {};
|
||||
readable[fd / 64] = uint64_t {1} << (fd % 64);
|
||||
auto writable = readable;
|
||||
const auto requested = readable;
|
||||
Check(Net::Select(fd + 1, readable.data(), writable.data(), nullptr, nullptr) == 2 &&
|
||||
readable == requested && writable == requested,
|
||||
"select reports an entropy source ready at once");
|
||||
Check(FileSystem::KernelClose(fd) == OK, "close entropy source");
|
||||
Check(Net::Select(fd + 1, readable.data(), nullptr, nullptr, nullptr) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EBADF,
|
||||
"select rejects a closed descriptor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2453,8 +2465,8 @@ int main(int argc, char** argv) {
|
||||
TestAioBatches();
|
||||
CheckSaveRename(temporary.Path(), "first-save");
|
||||
CheckSaveRename(temporary.Path(), "replacement-save");
|
||||
FileSystem::Shutdown();
|
||||
CheckSocketWakeup();
|
||||
FileSystem::Shutdown();
|
||||
CheckEtherAddressFormatting();
|
||||
#if defined(__linux__)
|
||||
TestNetCtlHostIpv4();
|
||||
|
||||
@@ -8547,16 +8547,16 @@ void TestShaderInfoCollection() {
|
||||
void TestImageBindingAbi() {
|
||||
using NumericClass = Libs::Graphics::Prospero::TextureNumericClass;
|
||||
|
||||
Check(ImageBindingCount == 48u &&
|
||||
Check(ImageBindingCount == 50u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Buffers) == 0u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 50u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 51u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 52u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 53u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 54u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::SharedMemory) == 55u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Count) == 56u,
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 51u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 52u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 53u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 54u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 55u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 56u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::SharedMemory) == 57u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u,
|
||||
"native descriptor binding anchors changed");
|
||||
|
||||
const std::array sampled_dimensions{
|
||||
@@ -8615,6 +8615,8 @@ void TestImageBindingAbi() {
|
||||
CheckBinding(ImageResourceClass::Storage, numeric_class, dimension,
|
||||
false);
|
||||
}
|
||||
CheckBinding(ImageResourceClass::Storage, numeric_class,
|
||||
Decoder::ImageDimension::Dim2DMsaa, false);
|
||||
}
|
||||
for (const auto dimension : storage_dimensions) {
|
||||
CheckBinding(ImageResourceClass::Storage, NumericClass::Uint, dimension,
|
||||
@@ -8658,11 +8660,15 @@ void TestImageBindingAbi() {
|
||||
image.numeric_class = NumericClass::Sint;
|
||||
Check(Invalid(image), "signed storage image received a descriptor binding");
|
||||
image.numeric_class = NumericClass::Float;
|
||||
image.dimension = Decoder::ImageDimension::Dim2DMsaa;
|
||||
image.dimension = Decoder::ImageDimension::Dim2DMsaaArray;
|
||||
Check(Invalid(image),
|
||||
"multisampled storage image received a descriptor binding");
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
"multisampled storage array received a descriptor binding");
|
||||
image.dimension = Decoder::ImageDimension::Dim2DMsaa;
|
||||
image.numeric_class = NumericClass::Uint;
|
||||
image.atomic = true;
|
||||
Check(Invalid(image), "multisampled atomic image received a descriptor binding");
|
||||
image.numeric_class = NumericClass::Float;
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
Check(Invalid(image), "float atomic image received a descriptor binding");
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "graphics/host_gpu/renderer/sync.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include <vulkan/vulkan_format_traits.hpp>
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
@@ -1619,6 +1620,17 @@ void CheckRectListShaders() {
|
||||
ValidateSpirv(name, position_only.evaluation);
|
||||
}
|
||||
|
||||
// Words without OpName/OpString/OpLine, which only graphics debug dumps emit.
|
||||
size_t SpirvCodeWords(const std::vector<u32> &spirv) {
|
||||
size_t words = spirv.size();
|
||||
for (size_t offset = 5; offset < spirv.size(); offset += spirv[offset] >> 16u) {
|
||||
const auto opcode = static_cast<spv::Op>(spirv[offset] & 0xffffu);
|
||||
if (opcode == spv::OpName || opcode == spv::OpString || opcode == spv::OpLine)
|
||||
words -= spirv[offset] >> 16u;
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
void CheckSpirvEmission(const TestCase &test, const std::vector<u32> &spirv) {
|
||||
std::vector<u32> result_types(spirv[3]);
|
||||
for (size_t offset = 5; offset < spirv.size();) {
|
||||
@@ -1679,6 +1691,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
||||
options.stage = ShaderType::Compute;
|
||||
options.profile = test.profile;
|
||||
options.dump_ir = true;
|
||||
options.emit_debug_info = true; // Checks find variables and functions by OpName.
|
||||
auto compute_info = test.compute_info;
|
||||
compute_info.host_subgroup_size = host_subgroup_size;
|
||||
options.input_info.compute = &compute_info;
|
||||
@@ -1750,7 +1763,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
||||
result.program.shader_info_complete = false;
|
||||
ShaderRecompiler::IR::CollectShaderInfo(result.program, options.input_info);
|
||||
result.spirv = ShaderRecompiler::Spirv::EmitProgram(
|
||||
result.program, options.input_info, 0u, *options.profile);
|
||||
result.program, options.input_info, 0u, *options.profile, true);
|
||||
const auto shader_data = result.program.bindings.descriptor_counts[
|
||||
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::ShaderData)];
|
||||
Require(test.name, "binding layout",
|
||||
@@ -1764,11 +1777,12 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
||||
ValidateSpirv(test.name, result.spirv);
|
||||
CheckSpirvEmission(test, result.spirv);
|
||||
if (test.max_spirv_words != 0u) {
|
||||
Require(test.name, "emitted shader size", result.spirv.size() <= test.max_spirv_words,
|
||||
"SPIR-V words=" + std::to_string(result.spirv.size()) +
|
||||
const auto words = SpirvCodeWords(result.spirv);
|
||||
Require(test.name, "emitted shader size", words <= test.max_spirv_words,
|
||||
"SPIR-V words=" + std::to_string(words) +
|
||||
", limit=" + std::to_string(test.max_spirv_words));
|
||||
std::printf("[compute] %s wave=%u SPIR-V words=%zu\n", test.name,
|
||||
result.program.wave_size, result.spirv.size());
|
||||
result.program.wave_size, words);
|
||||
}
|
||||
if (test.expected_buffer_resources) {
|
||||
std::vector<u32> resources;
|
||||
@@ -12464,6 +12478,212 @@ public:
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
// Cyberpunk 2077 copies its HTile D16 depth target through a kDepth k16UInt image load.
|
||||
void CheckDepthBitsCopy() {
|
||||
constexpr const char *name = "DepthBitsCopy";
|
||||
constexpr uintptr_t base = 0x0000000205000000ull;
|
||||
constexpr uint64_t allocation_size = 0x30000;
|
||||
constexpr uint64_t htile_address = base + 0x10000;
|
||||
constexpr uint64_t copy_address = base + 0x20000;
|
||||
constexpr u32 depth_code = 0x2000u;
|
||||
std::vector<u32> copy{EncodeMimg0(0x00, 0x1), EncodeMimg1(4, 0, 2),
|
||||
EncodeMimg0(0x08, 0x1), EncodeMimg1(4, 0, 0)};
|
||||
AppendEnd(©);
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(copy.data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = static_cast<u32>(copy.size() * sizeof(u32))});
|
||||
EnsureRuntimeContext();
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
||||
allocation_size, 0x10000, 0, &direct_offset) == 0,
|
||||
"depth allocation failed");
|
||||
void *mapped = reinterpret_cast<void *>(base);
|
||||
Require(name, "direct mapping",
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, allocation_size, 0x3, 0x10, direct_offset, 0x10000) == 0 &&
|
||||
mapped == reinterpret_cast<void *>(base),
|
||||
"depth mapping failed");
|
||||
std::memset(mapped, 0, allocation_size);
|
||||
RenderContext context(m_runtime_context);
|
||||
context.InitializeGpu(nullptr);
|
||||
LibKernel::Memory::InstallGpuResources(&context);
|
||||
context.GetGpu().SendCommandSync([&] {
|
||||
CommandProcessor processor(context, 0);
|
||||
processor.BufferInit();
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
auto &textures = context.GetTextureCache();
|
||||
auto &shaders = processor.GetShCtx();
|
||||
context.MapMemory(base, allocation_size);
|
||||
ImageDesc depth{};
|
||||
depth.type = BindingType::DepthTarget;
|
||||
depth.info.data = {base, 0x10000};
|
||||
depth.info.pixel_format = vk::Format::eD16Unorm;
|
||||
depth.info.guest_format = Prospero::BufferFormat::k16UNorm;
|
||||
depth.info.type = Prospero::ImageType::kColor2D;
|
||||
depth.info.extent = {256, 128, 1};
|
||||
depth.info.resources = {1, 1};
|
||||
depth.info.pitch = 256;
|
||||
depth.info.bytes_per_block = 2;
|
||||
depth.info.samples = 1;
|
||||
depth.info.tile_mode = Prospero::TileMode::kDepth;
|
||||
depth.info.UpdateSize();
|
||||
depth.info.metadata.kind = ImageMetadataKind::Htile;
|
||||
depth.info.metadata.range = {htile_address, 0x8000};
|
||||
depth.info.htile_initial_clear = false;
|
||||
depth.view_info.format = vk::Format::eD16Unorm;
|
||||
depth.view_info.type = vk::ImageViewType::e2D;
|
||||
depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
const auto depth_id = textures.FindImage(depth);
|
||||
Require(name, "HTile depth target", textures.FindDepthTarget(depth_id, depth) != nullptr,
|
||||
"the D16 depth target was not created");
|
||||
vk::ClearValue clear{};
|
||||
clear.depthStencil.depth = static_cast<float>(depth_code) / 65535.0f;
|
||||
TextureCacheTestAccess::ClearImage(textures, scheduler.Current(), depth_id,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}, clear);
|
||||
|
||||
// The captured T# with this depth target's size and HTile; the copy writes 2x2 texels.
|
||||
const ShaderTextureResource depth_bits{{static_cast<u32>(base >> 8u),
|
||||
(static_cast<u32>(Prospero::BufferFormat::k16UInt) << 20u) | (3u << 30u),
|
||||
0x3fu | (127u << 14u),
|
||||
DstSel(4, 0, 0, 1) | (static_cast<u32>(Prospero::TileMode::kDepth) << 20u) |
|
||||
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28u),
|
||||
0, 0x00700000u, 0x00280000u | (static_cast<u32>(htile_address >> 8u) << 24u),
|
||||
static_cast<u32>(htile_address >> 16u)}};
|
||||
const ShaderTextureResource target{{static_cast<u32>(copy_address >> 8u),
|
||||
(static_cast<u32>(Prospero::BufferFormat::k16UInt) << 20u) | (1u << 30u), 1u << 14u,
|
||||
DstSel(4, 5, 6, 7) | (static_cast<u32>(Prospero::TileMode::kLinear) << 20u) |
|
||||
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28u),
|
||||
0, 0x00700000u, 0, 0}};
|
||||
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(copy.data()),
|
||||
.num_thread_x = 2, .num_thread_y = 2, .num_thread_z = 1,
|
||||
.wave_size = 64, .user_sgpr = 16, .tidig_comp_cnt = 1});
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
shaders.SetCsUserSgpr(i, target.fields[i], HW::UserSgprType::Unknown);
|
||||
shaders.SetCsUserSgpr(8u + i, depth_bits.fields[i], HW::UserSgprType::Unknown);
|
||||
}
|
||||
processor.DispatchDirect(1, 1, 1, 0x41u);
|
||||
|
||||
ShaderRecompiler::IR::DescriptorValue value{};
|
||||
value.dword_count = 8;
|
||||
std::copy_n(target.fields, 8, value.dwords.begin());
|
||||
ShaderRecompiler::IR::ImageResource resource{};
|
||||
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
||||
resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
resource.written = true;
|
||||
const auto binding =
|
||||
RenderExecutorTestAccess::ResolveTexture(context.GetRenderExecutor(), resource, value);
|
||||
Require(name, "raw depth bits",
|
||||
ReadCachedTexel(name, context, binding.image_id, {}, {2, 2, 1}) ==
|
||||
std::vector<u32>(2, depth_code | depth_code << 16u),
|
||||
"the copy did not read the stored D16 codes");
|
||||
Require(name, "depth target retained", TextureCacheTestAccess::Contains(textures, depth_id),
|
||||
"reading depth bits replaced the HTile depth target");
|
||||
context.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
});
|
||||
context.ShutdownGpu();
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
||||
"depth mapping release failed");
|
||||
Require(name, "release",
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(direct_offset, allocation_size) == 0,
|
||||
"depth allocation release failed");
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
// Cyberpunk 2077 clears a rect of an MSAA color target from compute, one sample per store.
|
||||
void CheckMsaaStorageClear() {
|
||||
constexpr const char *name = "MsaaStorageClear";
|
||||
constexpr uintptr_t base = 0x0000000205000000ull;
|
||||
constexpr uint64_t allocation_size = 0x10000;
|
||||
// Captured CS 0xad6c86aca9c0916e: s[8:11] color, s[12:13] origin, s14 width,
|
||||
// s15 height << 16 | samples.
|
||||
std::vector<u32> clear{0xbfa00001u, 0xd7460001u, 0x04050611u, 0xd7460000u, 0x04010610u,
|
||||
0x7da21ef9u, 0x85060001u, 0x7da8000eu, 0xbf88000fu, 0x4a08000cu,
|
||||
0x4a0a020du, 0x996b800fu, 0xbeea0380u, 0xbf0a6b6au, 0xbf840009u,
|
||||
0x7e0c026au, 0x7e000208u, 0x7e020209u, 0x7e04020au, 0x7e06020bu,
|
||||
0x816a816au, 0xf0200f30u, 0x00000004u, 0xbf82fff5u, 0xbf810000u};
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(clear.data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = static_cast<u32>(clear.size() * sizeof(u32))});
|
||||
EnsureRuntimeContext();
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
||||
allocation_size, 0x10000, 0, &direct_offset) == 0,
|
||||
"MSAA allocation failed");
|
||||
void *mapped = reinterpret_cast<void *>(base);
|
||||
Require(name, "direct mapping",
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, allocation_size, 0x3, 0x10, direct_offset, 0x10000) == 0 &&
|
||||
mapped == reinterpret_cast<void *>(base),
|
||||
"MSAA mapping failed");
|
||||
RenderContext context(m_runtime_context);
|
||||
context.InitializeGpu(nullptr);
|
||||
LibKernel::Memory::InstallGpuResources(&context);
|
||||
context.GetGpu().SendCommandSync([&] {
|
||||
CommandProcessor processor(context, 0);
|
||||
processor.BufferInit();
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
auto &textures = context.GetTextureCache();
|
||||
auto &shaders = processor.GetShCtx();
|
||||
context.MapMemory(base, allocation_size);
|
||||
// 8x8 4x MSAA RGBA8: LAST_LEVEL and MAX_MIP hold log2(samples).
|
||||
const ShaderTextureResource target{{static_cast<u32>(base >> 8u),
|
||||
(static_cast<u32>(Prospero::BufferFormat::k8_8_8_8UNorm) << 20u) | (3u << 30u),
|
||||
1u | (7u << 14u),
|
||||
DstSel(4, 5, 6, 7) | (2u << 16u) |
|
||||
(static_cast<u32>(Prospero::TileMode::kRenderTarget) << 20u) |
|
||||
(static_cast<u32>(Prospero::ImageType::kColor2DMsaa) << 28u),
|
||||
0, 0x00700020u, 0, 0}};
|
||||
ShaderRecompiler::IR::DescriptorValue value{};
|
||||
value.dword_count = 8;
|
||||
std::copy_n(target.fields, 8, value.dwords.begin());
|
||||
ShaderRecompiler::IR::ImageResource resource{};
|
||||
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
||||
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
||||
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa;
|
||||
resource.written = true;
|
||||
const auto binding =
|
||||
RenderExecutorTestAccess::ResolveTexture(context.GetRenderExecutor(), resource, value);
|
||||
TextureCacheTestAccess::ClearImage(textures, scheduler.Current(), binding.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {});
|
||||
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(clear.data()),
|
||||
.num_thread_x = 8, .num_thread_y = 8, .num_thread_z = 1,
|
||||
.wave_size = 64, .user_sgpr = 16, .tgid_x_en = true,
|
||||
.tgid_y_en = true, .tidig_comp_cnt = 1});
|
||||
const std::array<u32, 8> args{std::bit_cast<u32>(1.0f), 0, 0, std::bit_cast<u32>(1.0f),
|
||||
1, 1, 2, (2u << 16u) | 4u};
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
shaders.SetCsUserSgpr(i, target.fields[i], HW::UserSgprType::Unknown);
|
||||
shaders.SetCsUserSgpr(8u + i, args[i], HW::UserSgprType::Unknown);
|
||||
}
|
||||
processor.DispatchDirect(1, 1, 1, 0x41u);
|
||||
for (const int row : {1, 2, 3}) {
|
||||
const u32 inside = row == 3 ? 0u : 0xff0000ffu;
|
||||
Require(name, "every sample in the rect",
|
||||
ReadCachedTexel(name, context, binding.image_id, {0, row, 0}, {4, 1, 1}) ==
|
||||
std::vector<u32>{0, inside, inside, 0},
|
||||
"the compute clear missed samples or wrote outside its rect");
|
||||
}
|
||||
context.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
});
|
||||
context.ShutdownGpu();
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
||||
"MSAA mapping release failed");
|
||||
Require(name, "release",
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(direct_offset, allocation_size) == 0,
|
||||
"MSAA allocation release failed");
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckSampledDccClear() {
|
||||
constexpr const char *name = "SampledDccClear";
|
||||
constexpr uintptr_t base = 0x0000000205000000ull;
|
||||
@@ -20872,6 +21092,7 @@ private:
|
||||
device_info.pNext = &conditional_rendering;
|
||||
vk::PhysicalDeviceFeatures device_features{};
|
||||
device_features.shaderStorageImageWriteWithoutFormat = true;
|
||||
device_features.shaderStorageImageMultisample = available_features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = true;
|
||||
device_features.sampleRateShading = true;
|
||||
device_features.shaderInt64 = true;
|
||||
@@ -37759,6 +37980,39 @@ template <bool rg, bool snorm = false> TestCase ImageSamplePacked8() {
|
||||
return test;
|
||||
}
|
||||
|
||||
// A kDepth k16UInt image load returns the stored D16 codes and integer swizzle constants.
|
||||
TestCase ImageLoadDepthBits() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = "ImageLoadDepthBits";
|
||||
constexpr std::array<u32, 4> depth{0x2000u, 0xc000u, 0xffffu, 0x0001u};
|
||||
for (u32 texel = 0; texel < depth.size(); ++texel) {
|
||||
AppendVMovU32(&test.code, 20, texel % 2u);
|
||||
AppendVMovU32(&test.code, 21, texel / 2u);
|
||||
test.code.push_back(EncodeMimg0(0x00, 0xf));
|
||||
test.code.push_back(EncodeMimg1(0, 20));
|
||||
for (u32 component = 0; component < 4; ++component) {
|
||||
AppendStoreVgpr(&test.code, component, texel * 4u + component);
|
||||
}
|
||||
test.expected.insert(test.expected.end(), {depth[texel], 0u, 0u, 1u});
|
||||
}
|
||||
AppendEnd(&test.code);
|
||||
test.image_width = test.image_height = 2;
|
||||
test.sampled_image_rgba = {depth[0] | depth[1] << 16u, depth[2] | depth[3] << 16u};
|
||||
test.sampled_image_format = vk::Format::eR16Unorm;
|
||||
test.sampled_image_dwords_per_pixel = 1;
|
||||
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k16UInt);
|
||||
test.user_data[1] |= 1u << 30u;
|
||||
test.user_data[2] = 1u << 14u;
|
||||
test.user_data[3] |= static_cast<u32>(Prospero::TileMode::kDepth) << 20u;
|
||||
test.user_data[50] = test.expected.size() * sizeof(u32);
|
||||
test.user_data[51] = 3u << 28u;
|
||||
test.has_user_data = true;
|
||||
test.image_descriptor_swizzle = DstSel(4, 0, 0, 1);
|
||||
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageLoadR128IgnoresAdjacentMaskSgprs() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -40580,7 +40834,7 @@ void CheckNestedPointerImageTable(VulkanHarness &vulkan) {
|
||||
compiled.program.info.samplers.size() == rows + 1u &&
|
||||
tools.Disassemble(compiled.spirv, &text) &&
|
||||
CountText(text, "OpImageSampleExplicitLod") == halves &&
|
||||
compiled.spirv.size() <= 8192u * halves,
|
||||
SpirvCodeWords(compiled.spirv) <= 8192u * halves,
|
||||
"59 sampler records hit the SGPR root limit or duplicated image sampling bodies");
|
||||
}
|
||||
if (large_samplers)
|
||||
@@ -43665,6 +43919,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ImageSamplePacked8<false>);
|
||||
AddCase(ImageSamplePacked8<true>);
|
||||
AddCase(ImageSamplePacked8<false, true>);
|
||||
AddCase(ImageLoadDepthBits);
|
||||
AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs);
|
||||
AddCase(ImageLoad1DUsesScalarCoordinate);
|
||||
AddCase(ImageGather2DInstructionWith1DDescriptor);
|
||||
@@ -49095,6 +49350,94 @@ void CheckPm4IndirectControlFlow(RenderContext &renderer) {
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
// waitUntilSafeForRendering stalls only its own command processor (SDK), and a display buffer
|
||||
// stays busy while its flip is pending or while it is on screen.
|
||||
void CheckWaitUntilSafeForRendering() {
|
||||
constexpr const char *name = "WaitUntilSafeForRendering";
|
||||
Require(name, "video", SDL_InitSubSystem(SDL_INIT_VIDEO), "SDL video init failed");
|
||||
WindowContext window;
|
||||
window.graphic_ctx.screen_width = 64;
|
||||
window.graphic_ctx.screen_height = 64;
|
||||
window.window = SDL_CreateWindow(name, 64, 64, SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
|
||||
Require(name, "window", window.window != nullptr, "hidden Vulkan window failed");
|
||||
window.CreateVulkan();
|
||||
auto &video_out = VideoOut::VideoOutInit(64, 64, *window.presenter);
|
||||
auto &renderer = *window.render_context;
|
||||
renderer.InitializeGpu(&video_out);
|
||||
auto &gpu = renderer.GetGpu();
|
||||
|
||||
constexpr int user_system = 255;
|
||||
constexpr int bus_main = 0;
|
||||
constexpr int flip_mode_vsync = 1;
|
||||
// Special buffer indices flip without a registered surface.
|
||||
constexpr int index_blank = -1;
|
||||
constexpr int index_black = -2;
|
||||
const int handle = VideoOut::VideoOutOpen(user_system, bus_main, 0, nullptr);
|
||||
Require(name, "open", handle > 0, "video-out open failed");
|
||||
const auto wait_until = [](const auto &done) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (!done() && std::chrono::steady_clock::now() < deadline) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
return done();
|
||||
};
|
||||
const auto address = [](const void *value) { return reinterpret_cast<uint64_t>(value); };
|
||||
|
||||
// Hold the graphics queue so the CPU flip of BLACK, prepared behind it, stays pending.
|
||||
std::atomic_uint32_t gate{0};
|
||||
std::array<uint32_t, 7> hold{};
|
||||
hold[0] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0);
|
||||
hold[1] = 0x10u | 3u;
|
||||
hold[2] = static_cast<uint32_t>(address(&gate));
|
||||
hold[3] = static_cast<uint32_t>(address(&gate) >> 32u);
|
||||
hold[4] = 1;
|
||||
hold[5] = UINT32_MAX;
|
||||
gpu.Submit(hold, {});
|
||||
Require(name, "flip black",
|
||||
VideoOut::VideoOutSubmitFlip(handle, index_black, flip_mode_vsync, 0) == 0,
|
||||
"CPU flip failed");
|
||||
|
||||
std::atomic_uint32_t rendered{0};
|
||||
std::array<uint32_t, 12> render{};
|
||||
render[0] = KYTY_PM4(7, Pm4::IT_NOP, Pm4::R_WAIT_FLIP_DONE);
|
||||
render[1] = static_cast<uint32_t>(handle);
|
||||
render[2] = static_cast<uint32_t>(index_black);
|
||||
render[7] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
||||
render[9] = static_cast<uint32_t>(address(&rendered));
|
||||
render[10] = static_cast<uint32_t>(address(&rendered) >> 32u);
|
||||
render[11] = 1;
|
||||
gpu.SubmitCompute(0x20, render);
|
||||
|
||||
std::binary_semaphore lane{0};
|
||||
std::jthread command_lane([&] {
|
||||
gpu.SendCommandSync([] {});
|
||||
lane.release();
|
||||
});
|
||||
Require(name, "command lane", lane.try_acquire_for(std::chrono::seconds(2)),
|
||||
"a busy display buffer blocked the GPU thread");
|
||||
Require(name, "pending", rendered.load() == 0,
|
||||
"rendering started while its display buffer had a pending flip");
|
||||
|
||||
gate.store(1);
|
||||
Require(name, "flip shown", wait_until([handle] { return VideoOut::VideoOutIsFlipPending(handle) == 0; }),
|
||||
"the BLACK flip was not presented");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
Require(name, "displayed", rendered.load() == 0,
|
||||
"rendering started while its display buffer was on screen");
|
||||
|
||||
Require(name, "flip blank",
|
||||
VideoOut::VideoOutSubmitFlip(handle, index_blank, flip_mode_vsync, 0) == 0,
|
||||
"CPU flip failed");
|
||||
Require(name, "resume", wait_until([&] { return rendered.load() == 1; }),
|
||||
"rendering did not resume once its display buffer left the screen");
|
||||
|
||||
gpu.WaitForIdle();
|
||||
Require(name, "close", VideoOut::VideoOutClose(handle) == 0, "video-out close failed");
|
||||
renderer.ShutdownGpu();
|
||||
VideoOut::VideoOutShutdown();
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckPm4WaitResume(RenderContext &renderer) {
|
||||
GraphicsInitJmpTables();
|
||||
CommandProcessor processor(renderer, 0);
|
||||
@@ -49617,6 +49960,17 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, ImageSamplePacked8<false, true>());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--msaa-storage-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckMsaaStorageClear();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--depth-bits-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckDepthBitsCopy();
|
||||
RunCase(&vulkan, ImageLoadDepthBits());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, ImageLoadSintUsesSignedSampledImage<false>());
|
||||
@@ -49974,6 +50328,10 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckScalarLeafCall();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--wait-until-safe-for-rendering-only") == 0) {
|
||||
CheckWaitUntilSafeForRendering();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--gpu-command-lane-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckGpuCommandLane();
|
||||
@@ -50504,6 +50862,8 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
||||
vulkan.CheckRenderExecutorColorMetadataClear();
|
||||
vulkan.CheckSampledDccClear();
|
||||
vulkan.CheckDepthBitsCopy();
|
||||
vulkan.CheckMsaaStorageClear();
|
||||
vulkan.CheckRenderExecutorColorStandardTileDiscovery();
|
||||
vulkan.CheckRenderExecutorColorDepthTileDiscovery();
|
||||
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
||||
@@ -50527,6 +50887,8 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckGpuSuspendPoint();
|
||||
vulkan.CheckGpuCommandLane();
|
||||
vulkan.CheckScalarLeafCall();
|
||||
// Last: its window installs global GPU resources.
|
||||
CheckWaitUntilSafeForRendering();
|
||||
if (skipped_device_checks) {
|
||||
std::printf(
|
||||
"ShaderRecompilerComputeTests: device rasterization checks skipped, this "
|
||||
|
||||
@@ -99,6 +99,7 @@ ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.user_data = user_data;
|
||||
options.emit_debug_info = true; // Checks find variables and functions by OpName.
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex:
|
||||
options.input_info.vertex = &vertex;
|
||||
@@ -403,6 +404,11 @@ SpirvMetrics MeasureSpirv(const std::vector<uint32_t> &binary) {
|
||||
if (word_count == 0 || i + word_count > binary.size()) {
|
||||
break;
|
||||
}
|
||||
if (opcode == spv::OpName || opcode == spv::OpString || opcode == spv::OpLine) {
|
||||
metrics.words -= word_count; // Budgets measure the module without debug info.
|
||||
i += word_count;
|
||||
continue;
|
||||
}
|
||||
metrics.instructions++;
|
||||
switch (opcode) {
|
||||
case 11u: // OpExtInstImport
|
||||
@@ -10354,7 +10360,7 @@ void TestNewShaderRecompilerPlanningOnlyLoads() {
|
||||
ir.Emit(IR::ValueOpcode::ReferenceU32, {read});
|
||||
program.shader_info_complete = false;
|
||||
IR::CollectShaderInfo(program, options.input_info);
|
||||
const auto spirv = Spirv::EmitProgram(program, options.input_info);
|
||||
const auto spirv = Spirv::EmitProgram(program, options.input_info, 0, {}, true);
|
||||
CheckSpirvBinaryValidates(spirv);
|
||||
Check(spirv == result.spirv,
|
||||
"planning-only loads or references emitted GPU operands/instructions");
|
||||
@@ -11100,6 +11106,7 @@ void TestMeshExportStorage() {
|
||||
options.stage = ShaderType::Mesh;
|
||||
options.input_info.vertex = &input;
|
||||
options.user_data = user_data;
|
||||
options.emit_debug_info = true; // The checks find push data by its OpName.
|
||||
struct Case {
|
||||
uint32_t subgroup_size, push_data_start;
|
||||
bool split;
|
||||
@@ -14352,7 +14359,8 @@ void TestNewShaderRecompilerNativeBindingPlan() {
|
||||
const auto bindings = result.program.bindings;
|
||||
result.program.bindings = {};
|
||||
result.program.info.buffers[0].descriptor_index = UINT32_MAX;
|
||||
Check(ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info) == result.spirv &&
|
||||
Check(ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info, 0, {}, true) ==
|
||||
result.spirv &&
|
||||
result.program.bindings == bindings &&
|
||||
result.program.info.buffers[0].descriptor_index == 0,
|
||||
"repeated emission did not rebuild descriptor slots deterministically");
|
||||
@@ -16423,11 +16431,15 @@ void TestRepeatedExportsHaveOneInterface() {
|
||||
void CheckSpirvBlockSourceMapping(TestCompileResult &result) {
|
||||
using namespace ShaderRecompiler;
|
||||
const auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpString) == 0 &&
|
||||
SpirvInstructionOpcodeCount(result.spirv, spv::OpLine) == 0,
|
||||
"default emission unexpectedly contains source mapping");
|
||||
const auto plain = Spirv::EmitProgram(result.program, options.input_info);
|
||||
Check(SpirvInstructionOpcodeCount(plain, spv::OpString) == 0 &&
|
||||
SpirvInstructionOpcodeCount(plain, spv::OpLine) == 0 &&
|
||||
SpirvInstructionOpcodeCount(plain, spv::OpName) == 0,
|
||||
"default emission unexpectedly contains debug info");
|
||||
const auto mapped =
|
||||
Spirv::EmitProgram(result.program, options.input_info, 0, {}, true);
|
||||
Check(SpirvInstructionOpcodeCount(mapped, spv::OpName) != 0,
|
||||
"debug emission omitted names");
|
||||
CheckSpirvBinaryValidates(mapped);
|
||||
uint32_t source = 0;
|
||||
uint32_t label = 0;
|
||||
@@ -16456,7 +16468,7 @@ void CheckSpirvBlockSourceMapping(TestCompileResult &result) {
|
||||
}
|
||||
Check(source != 0 && lines >= result.program.blocks.size(),
|
||||
"debug emission omitted native block source mapping");
|
||||
Check(Spirv::EmitProgram(result.program, options.input_info) == result.spirv,
|
||||
Check(Spirv::EmitProgram(result.program, options.input_info) == plain,
|
||||
"debug re-emission changed the default binary");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user