diff --git a/src/xenia/apu/xma_context.cc b/src/xenia/apu/xma_context.cc index 8d6965a8c..359342061 100644 --- a/src/xenia/apu/xma_context.cc +++ b/src/xenia/apu/xma_context.cc @@ -851,7 +851,7 @@ bool XmaContext::ConvertFrame(const uint8_t** samples, int num_channels, auto in = reinterpret_cast(samples[j]); // Raw samples sometimes aren't within [-1, 1] - float scaled_sample = xe::saturate(in[i]) * scale; + float scaled_sample = xe::saturate_signed(in[i]) * scale; // Convert the sample and output it in big endian. auto sample = static_cast(scaled_sample); diff --git a/src/xenia/base/math.h b/src/xenia/base/math.h index 4a54647b8..291e23a27 100644 --- a/src/xenia/base/math.h +++ b/src/xenia/base/math.h @@ -57,8 +57,16 @@ constexpr T round_up(T value, V multiple) { return value ? (((value + multiple - 1) / multiple) * multiple) : multiple; } -constexpr float saturate(float value) { - return std::max(std::min(1.0f, value), -1.0f); +// Using the same conventions as in shading languages, returning 0 for NaN. +// std::max is `a < b ? b : a`, thus in case of NaN, the first argument is +// always returned. Also -0 is not < +0, so +0 is also chosen for it. +template +constexpr T saturate_unsigned(T value) { + return std::min(static_cast(1.0f), std::max(static_cast(0.0f), value)); +} +template +constexpr T saturate_signed(T value) { + return std::min(static_cast(1.0f), std::max(static_cast(-1.0f), value)); } // Gets the next power of two value that is greater than or equal to the given diff --git a/src/xenia/gpu/d3d12/d3d12_graphics_system.cc b/src/xenia/gpu/d3d12/d3d12_graphics_system.cc index 12259a1a3..d84d68969 100644 --- a/src/xenia/gpu/d3d12/d3d12_graphics_system.cc +++ b/src/xenia/gpu/d3d12/d3d12_graphics_system.cc @@ -9,6 +9,8 @@ #include "xenia/gpu/d3d12/d3d12_graphics_system.h" +#include + #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/gpu/d3d12/d3d12_command_processor.h" diff --git a/src/xenia/gpu/draw_util.cc b/src/xenia/gpu/draw_util.cc index 8612038a0..21db6f42b 100644 --- a/src/xenia/gpu/draw_util.cc +++ b/src/xenia/gpu/draw_util.cc @@ -409,12 +409,13 @@ void GetHostViewportInfo(const RegisterFile& regs, uint32_t resolution_scale, float axis_0 = offset_axis - scale_axis_abs; float axis_1 = offset_axis + scale_axis_abs; float axis_max_unscaled_float = float(xy_max_unscaled[i]); - // fmax to drop NaN and < 0, min as float (axis_max_unscaled_float is well - // below 2^24) to safely drop very large values. + // max(0.0f, xy) drops NaN and < 0 - max picks the first argument in the + // !(a < b) case (always for NaN), min as float (axis_max_unscaled_float + // is well below 2^24) to safely drop very large values. uint32_t axis_0_int = - uint32_t(std::min(std::fmax(axis_0, 0.0f), axis_max_unscaled_float)); + uint32_t(std::min(axis_max_unscaled_float, std::max(0.0f, axis_0))); uint32_t axis_1_int = - uint32_t(std::min(std::fmax(axis_1, 0.0f), axis_max_unscaled_float)); + uint32_t(std::min(axis_max_unscaled_float, std::max(0.0f, axis_1))); uint32_t axis_extent_int = axis_1_int - axis_0_int; viewport_info_out.xy_offset[i] = axis_0_int * resolution_scale; viewport_info_out.xy_extent[i] = axis_extent_int * resolution_scale; @@ -517,9 +518,8 @@ void GetHostViewportInfo(const RegisterFile& regs, uint32_t resolution_scale, // extension. But cases when this really matters are yet to be found - // trying to fix this will result in more correct depth values, but // incorrect clipping. - z_min = std::min(std::fmax(host_clip_offset_z, 0.0f), 1.0f); - z_max = std::min(std::fmax(host_clip_offset_z + host_clip_scale_z, 0.0f), - 1.0f); + z_min = xe::saturate_unsigned(host_clip_offset_z); + z_max = xe::saturate_unsigned(host_clip_offset_z + host_clip_scale_z); // Direct3D 12 doesn't allow reverse depth range - on some drivers it // works, on some drivers it doesn't, actually, but it was never // explicitly allowed by the specification. diff --git a/src/xenia/gpu/render_target_cache.cc b/src/xenia/gpu/render_target_cache.cc index f87f7272d..573cb29a0 100644 --- a/src/xenia/gpu/render_target_cache.cc +++ b/src/xenia/gpu/render_target_cache.cc @@ -585,10 +585,11 @@ bool RenderTargetCache::Update(bool is_rasterization_done, // Using floor, or, rather, truncation (because maxing with zero anyway) // similar to how viewport scissoring behaves on real AMD, Intel and Nvidia // GPUs on Direct3D 12, also like in draw_util::GetHostViewportInfo. - // fmax to drop NaN and < 0, min as float (height_used is well below 2^24) - // to safely drop very large values. - height_used = uint32_t( - std::min(std::fmax(viewport_bottom, 0.0f), float(height_used))); + // max(0.0f, viewport_bottom) to drop NaN and < 0 - max picks the first + // argument in the !(a < b) case (always for NaN), min as float (height_used + // is well below 2^24) to safely drop very large values. + height_used = + uint32_t(std::min(float(height_used), std::max(0.0f, viewport_bottom))); } int32_t scissor_bottom = int32_t(regs.Get().br_y); diff --git a/src/xenia/gpu/trace_viewer.cc b/src/xenia/gpu/trace_viewer.cc index 5297d6856..f632267fa 100644 --- a/src/xenia/gpu/trace_viewer.cc +++ b/src/xenia/gpu/trace_viewer.cc @@ -1031,7 +1031,7 @@ void ProgressBar(float frac, float width, float height = 0, if (height == 0) { height = ImGui::GetTextLineHeightWithSpacing(); } - frac = xe::saturate(frac); + frac = xe::saturate_unsigned(frac); const auto fontAtlas = ImGui::GetIO().Fonts;