graphics: render packed 11-11-10 UNorm targets with exact rounding

Co-authored-by: fxpw <fxpw@outlook.com>
This commit is contained in:
nmzik
2026-10-15 04:35:56 +02:00
parent fb213e8ae5
commit b4406b0467
11 changed files with 291 additions and 10 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k16_16UInt, 4, 0, 4, true, true},
{BufferFormat::k16_16SInt, 4, 0, 4, true, false, true},
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UNorm, 4, 0, 0, true, false},
{BufferFormat::k11_11_10UNorm, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
@@ -64,7 +64,10 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
const auto backing_format = encoding.buffer_format == Prospero::BufferFormat::k11_11_10UNorm
? Prospero::RemapTextureFormat(encoding.buffer_format)
: encoding.buffer_format;
const auto host_format = ResolveHostFormat(backing_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/threads.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
@@ -681,6 +682,28 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
ShaderParams pixel_params;
if (pixel_active) {
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
for (uint32_t export_index = 0; export_index < 8u; export_index++) {
const auto slot = ShaderPixelExportTarget(pixel_info.target_shader_mask, export_index);
if (slot >= 8u || pixel_info.target_output_mode[export_index] == 0u) continue;
const auto& rt = context.GetRenderTarget(slot);
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
if (rt.base.addr == 0 || target_mask == 0u) continue;
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
const auto mapping = pixel_info.target_export_mapping[export_index];
const auto shader_mask = render_target_mask_slot(pixel_info.target_shader_mask, slot);
const bool blend = context.GetBlendControl(slot).enable && !rt.info.blend_bypass;
if (blend || (mapping.ApplyMask(target_mask) & 7u) != 7u ||
(mapping.ApplyMask(shader_mask) & 7u) != 7u ||
pixel_info.target_output_mode[export_index] != 9u) {
EXIT("unsupported packed UNorm render-target export: slot=%u export=%u mode=%u "
"target_mask=0x%x shader_mask=0x%x mapping=0x%02x blend=%u\n",
slot, export_index, pixel_info.target_output_mode[export_index], target_mask,
shader_mask, mapping.packed, static_cast<uint32_t>(blend));
}
pixel_info.target_packed_unorm_mask |= 1u << export_index;
pixel_info.target_round_mode |= static_cast<uint8_t>(rt.info.round_mode) << export_index;
}
if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) {
pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle;
} else if (context.GetModeControl().provoking_vtx_last) {
@@ -803,6 +826,9 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
static_params.color_mask[slot] = colors[i].export_mapping.ApplyMask(
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
if (colors[i].desc.info.guest_format == Prospero::BufferFormat::k11_11_10UNorm) {
static_params.color_mask[slot] = 1u;
}
rendering.color_formats[slot] = colors[i].desc.view_info.format;
if (attachment_samples == 0) {
attachment_samples = colors[i].desc.info.samples;
@@ -567,6 +567,28 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
mapping.Map(3));
value = mapped;
}
if ((state.input_info.pixel->target_packed_unorm_mask & (1u << exp.index)) != 0u) {
if (exp.compr || MrtOutputMode(state, exp) != 9u ||
(mapping.ApplyMask(exp.en) & 7u) != 7u) {
EXIT("unsupported packed UNorm EXP: export=%u mode=%u en=0x%x compr=%u\n",
exp.index, MrtOutputMode(state, exp), exp.en, static_cast<uint32_t>(exp.compr));
}
const auto info = Format::GetFormatInfo(Prospero::BufferFormat::k11_11_10UNorm);
const auto rounding = (state.input_info.pixel->target_round_mode & (1u << exp.index)) != 0
? UnormRounding::Truncate : UnormRounding::HalfUp;
const auto raw = Unary(state, spv::OpBitcast, TypeU32Vector(state, 4), value);
auto packed = ConstantU32(state, 0);
for (uint32_t component = 0; component < info.component_count; component++) {
const auto encoded = EncodePackedColorComponent(
state, info, component, ExportRawComponent(ctx, raw, component), 32u, rounding);
packed = component == 0u ? encoded
: Binary(state, spv::OpBitwiseOr, TypeU32(state), packed, encoded);
}
value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), value,
packed, ConstantU32(state, 0), ConstantU32(state, 0),
ConstantU32(state, 0));
}
}
if (exp.kind == IR::ExportTargetKind::Position &&
state.input_info.vertex->clip_space.enabled) {
@@ -456,11 +456,15 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits);
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t raw);
enum class UnormRounding { RoundEven, HalfUp, Truncate };
uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits);
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding = UnormRounding::RoundEven);
uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits);
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding = UnormRounding::RoundEven);
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode);
@@ -429,7 +429,9 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
}
uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits) {
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding) {
EXIT_IF(rounding != UnormRounding::RoundEven && info.type != Format::ComponentType::Unorm);
const auto bits = info.component_bits[component];
if (data_bits == 16u) {
if (info.type == Format::ComponentType::Uint) {
@@ -464,8 +466,12 @@ uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::Buffer
value = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper));
if (normalized) {
value = EmitFPRoundEven32(
state, EmitFPMul32(state, value, ConstantF32Value(state, max_value)));
value = EmitFPMul32(state, value, ConstantF32Value(state, max_value));
if (rounding == UnormRounding::RoundEven) {
value = EmitFPRoundEven32(state, value);
} else if (rounding == UnormRounding::HalfUp) {
value = EmitFPAdd32(state, value, ConstantF32Value(state, 0.5f));
}
}
// Float-to-integer conversion truncates scaled values toward zero.
value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU,
@@ -481,8 +487,9 @@ uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::Buffer
}
uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits) {
auto value = EncodeFormattedStoreComponent(state, info, component, data, data_bits);
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding) {
auto value = EncodeFormattedStoreComponent(state, info, component, data, data_bits, rounding);
if (info.type == Format::ComponentType::Uint) {
value = EmitUMin32(state, value,
ConstantU32(state, (1u << info.component_bits[component]) - 1u));
@@ -663,7 +663,8 @@ void DefineOutputs(EmitterState& state) {
binding.kind == IR::StageOutputKind::Mrt &&
state.program.stage == ShaderType::Pixel &&
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
state.input_info.pixel->target_output_mode[binding.index] == 7u;
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
binding.debug_name.c_str());
+1
View File
@@ -675,6 +675,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
key.push_back(static_cast<uint32_t>(info.dual_source_blending));
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
key.push_back(info.target_packed_unorm_mask | (uint32_t {info.target_round_mode} << 8u));
const auto mask = info.target_shader_mask;
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
+3
View File
@@ -175,6 +175,9 @@ struct ShaderPixelInputInfo {
uint8_t target_output_mode[8] = {};
uint32_t target_shader_mask = UINT32_MAX;
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint8_t target_packed_unorm_mask = 0;
// CB round mode per converted export: zero adds half an LSB, one truncates.
uint8_t target_round_mode = 0;
uint32_t scratch_size_dwords = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;