diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h index 8020faee8..9703243fd 100644 --- a/src/video_core/regs_framebuffer.h +++ b/src/video_core/regs_framebuffer.h @@ -15,6 +15,12 @@ namespace Pica { struct FramebufferRegs { + enum class FragmentOperationMode : u32 { + Default = 0, + Gas = 1, + Shadow = 3, + }; + enum class LogicOp : u32 { Clear = 0, And = 1, @@ -84,6 +90,7 @@ struct FramebufferRegs { struct { union { + BitField<0, 2, FragmentOperationMode> fragment_operation_mode; // If false, logic blending is used BitField<8, 1, u32> alphablend_enable; }; @@ -274,7 +281,14 @@ struct FramebufferRegs { ASSERT_MSG(false, "Unknown depth format %u", static_cast(format)); } - INSERT_PADDING_WORDS(0x20); + INSERT_PADDING_WORDS(0x10); // Gas related registers + + union { + BitField<0, 16, u32> constant; // float1.5.10 + BitField<16, 16, u32> linear; // float1.5.10 + } shadow; + + INSERT_PADDING_WORDS(0xF); }; static_assert(sizeof(FramebufferRegs) == 0x40 * sizeof(u32), diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h index 69d1b2b05..8ef1d2136 100644 --- a/src/video_core/regs_lighting.h +++ b/src/video_core/regs_lighting.h @@ -187,9 +187,15 @@ struct LightingRegs { BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1 union { + BitField<0, 1, u32> enable_shadow; BitField<2, 2, LightingFresnelSelector> fresnel_selector; BitField<4, 4, LightingConfig> config; + BitField<16, 1, u32> shadow_primary; + BitField<17, 1, u32> shadow_secondary; + BitField<18, 1, u32> shadow_invert; + BitField<19, 1, u32> shadow_alpha; BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2 + BitField<24, 2, u32> shadow_selector; BitField<27, 1, u32> clamp_highlights; BitField<28, 2, LightingBumpMode> bump_mode; BitField<30, 1, u32> disable_bump_renorm; @@ -198,6 +204,9 @@ struct LightingRegs { union { u32 raw; + // Each bit specifies whether shadow should be applied for the corresponding light. + BitField<0, 8, u32> disable_shadow; + // Each bit specifies whether spot light attenuation should be applied for the corresponding // light. BitField<8, 8, u32> disable_spot_atten; @@ -224,6 +233,10 @@ struct LightingRegs { return (config1.disable_spot_atten & (1 << index)) != 0; } + bool IsShadowDisabled(unsigned index) const { + return (config1.disable_shadow & (1 << index)) != 0; + } + union { BitField<0, 8, u32> index; ///< Index at which to set data in the LUT BitField<8, 5, u32> type; ///< Type of LUT for which to set data diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index 0b09f2299..ff458cc57 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -158,7 +158,12 @@ struct TexturingRegs { return address * 8; } - INSERT_PADDING_WORDS(0x3); + union { + BitField<0, 1, u32> orthographic; // 0: enable perspective divide + BitField<1, 23, u32> bias; // 23-bit fraction + } shadow; + + INSERT_PADDING_WORDS(0x2); BitField<0, 4, TextureFormat> texture0_format; BitField<0, 1, u32> fragment_lighting_enable; INSERT_PADDING_WORDS(0x1); diff --git a/src/video_core/swrasterizer/framebuffer.cpp b/src/video_core/swrasterizer/framebuffer.cpp index fb41ce4f5..f8312b3c1 100644 --- a/src/video_core/swrasterizer/framebuffer.cpp +++ b/src/video_core/swrasterizer/framebuffer.cpp @@ -359,5 +359,54 @@ u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op) { UNREACHABLE(); }; +// Decode/Encode for shadow map format. It is similar to D24S8 format, but the depth field is in +// big-endian +static const Math::Vec2 DecodeD24S8Shadow(const u8* bytes) { + return {static_cast((bytes[0] << 16) | (bytes[1] << 8) | bytes[2]), bytes[3]}; +} + +static void EncodeD24X8Shadow(u32 depth, u8* bytes) { + bytes[2] = depth & 0xFF; + bytes[1] = (depth >> 8) & 0xFF; + bytes[0] = (depth >> 16) & 0xFF; +} + +static void EncodeX24S8Shadow(u8 stencil, u8* bytes) { + bytes[3] = stencil; +} + +void DrawShadowMapPixel(int x, int y, u32 depth, u8 stencil) { + const auto& framebuffer = g_state.regs.framebuffer.framebuffer; + const auto& shadow = g_state.regs.framebuffer.shadow; + const PAddr addr = framebuffer.GetColorBufferPhysicalAddress(); + + y = framebuffer.height - y; + + const u32 coarse_y = y & ~7; + u32 bytes_per_pixel = 4; + u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + + coarse_y * framebuffer.width * bytes_per_pixel; + u8* dst_pixel = Memory::GetPhysicalPointer(addr) + dst_offset; + + auto ref = DecodeD24S8Shadow(dst_pixel); + u32 ref_z = ref.x; + u32 ref_s = ref.y; + + if (depth < ref_z) { + if (stencil == 0) { + EncodeD24X8Shadow(depth, dst_pixel); + } else { + float16 constant = float16::FromRaw(shadow.constant); + float16 linear = float16::FromRaw(shadow.linear); + float16 x = float16::FromFloat32(static_cast(depth) / ref_z); + float16 stencil_new = float16::FromFloat32(stencil) / (constant + linear * x); + stencil = static_cast(MathUtil::Clamp(stencil_new.ToFloat32(), 0.0f, 255.0f)); + + if (stencil < ref_s) + EncodeX24S8Shadow(stencil, dst_pixel); + } + } +} + } // namespace Rasterizer } // namespace Pica diff --git a/src/video_core/swrasterizer/framebuffer.h b/src/video_core/swrasterizer/framebuffer.h index 4a32a4979..0a1c42669 100644 --- a/src/video_core/swrasterizer/framebuffer.h +++ b/src/video_core/swrasterizer/framebuffer.h @@ -25,5 +25,7 @@ Math::Vec4 EvaluateBlendEquation(const Math::Vec4& src, const Math::Vec4 u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op); +void DrawShadowMapPixel(int x, int y, u32 depth, u8 stencil); + } // namespace Rasterizer } // namespace Pica diff --git a/src/video_core/swrasterizer/lighting.cpp b/src/video_core/swrasterizer/lighting.cpp index f7d317977..fb345e077 100644 --- a/src/video_core/swrasterizer/lighting.cpp +++ b/src/video_core/swrasterizer/lighting.cpp @@ -25,6 +25,16 @@ std::tuple, Math::Vec4> ComputeFragmentsColors( const Math::Quaternion& normquat, const Math::Vec3& view, const Math::Vec4 (&texture_color)[4]) { + Math::Vec4 shadow; + if (lighting.config0.enable_shadow) { + shadow = texture_color[lighting.config0.shadow_selector].Cast() / 255.0f; + if (lighting.config0.shadow_invert) { + shadow = Math::MakeVec(1.0f, 1.0f, 1.0f, 1.0f) - shadow; + } + } else { + shadow = Math::MakeVec(1.0f, 1.0f, 1.0f, 1.0f); + } + Math::Vec3 surface_normal; Math::Vec3 surface_tangent; @@ -278,11 +288,38 @@ std::tuple, Math::Vec4> ComputeFragmentsColors( } auto diffuse = - light_config.diffuse.ToVec3f() * dot_product + light_config.ambient.ToVec3f(); - diffuse_sum += Math::MakeVec(diffuse * dist_atten * spot_atten, 0.0f); + (light_config.diffuse.ToVec3f() * dot_product + light_config.ambient.ToVec3f()) * + dist_atten * spot_atten; + auto specular = (specular_0 + specular_1) * clamp_highlights * dist_atten * spot_atten; - specular_sum += Math::MakeVec( - (specular_0 + specular_1) * clamp_highlights * dist_atten * spot_atten, 0.0f); + if (!lighting.IsShadowDisabled(num)) { + if (lighting.config0.shadow_primary) { + diffuse = diffuse * shadow.xyz(); + } + if (lighting.config0.shadow_secondary) { + specular = specular * shadow.xyz(); + } + } + + diffuse_sum += Math::MakeVec(diffuse, 0.0f); + specular_sum += Math::MakeVec(specular, 0.0f); + } + + if (lighting.config0.shadow_alpha) { + // Alpha shadow also uses the Fresnel selecotr to determine which alpha to apply + // Enabled for diffuse lighting alpha component + if (lighting.config0.fresnel_selector == + LightingRegs::LightingFresnelSelector::PrimaryAlpha || + lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { + diffuse_sum.a() *= shadow.w; + } + + // Enabled for the specular lighting alpha component + if (lighting.config0.fresnel_selector == + LightingRegs::LightingFresnelSelector::SecondaryAlpha || + lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { + specular_sum.a() *= shadow.w; + } } diffuse_sum += Math::MakeVec(lighting.global_ambient.ToVec3f(), 0.0f); diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index b34261e4f..f2666deaf 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -74,8 +74,9 @@ static int SignedArea(const Math::Vec2& vtx1, const Math::Vec2 }; /// Convert a 3D vector for cube map coordinates to 2D texture coordinates along with the face name -static std::tuple ConvertCubeCoord(float24 u, float24 v, float24 w, - const TexturingRegs& regs) { +static std::tuple ConvertCubeCoord(float24 u, float24 v, + float24 w, + const TexturingRegs& regs) { const float abs_u = std::abs(u.ToFloat32()); const float abs_v = std::abs(v.ToFloat32()); const float abs_w = std::abs(w.ToFloat32()); @@ -112,8 +113,9 @@ static std::tuple ConvertCubeCoord(float24 u, float24 v x = u; z = w; } + float24 z_abs = float24::FromFloat32(std::abs(z.ToFloat32())); const float24 half = float24::FromFloat32(0.5f); - return std::make_tuple(x / z * half + half, y / z * half + half, addr); + return std::make_tuple(x / z * half + half, y / z * half + half, z_abs, addr); } MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240)); @@ -331,13 +333,16 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve // Only unit 0 respects the texturing type (according to 3DBrew) // TODO: Refactor so cubemaps and shadowmaps can be handled PAddr texture_address = texture.config.GetPhysicalAddress(); + float24 shadow_z; if (i == 0) { switch (texture.config.type) { case TexturingRegs::TextureConfig::Texture2D: break; + case TexturingRegs::TextureConfig::ShadowCube: case TexturingRegs::TextureConfig::TextureCube: { auto w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); - std::tie(u, v, texture_address) = ConvertCubeCoord(u, v, w, regs.texturing); + std::tie(u, v, shadow_z, texture_address) = + ConvertCubeCoord(u, v, w, regs.texturing); break; } case TexturingRegs::TextureConfig::Projection2D: { @@ -346,6 +351,16 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve v /= tc0_w; break; } + case TexturingRegs::TextureConfig::Shadow2D: { + auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); + if (!regs.texturing.shadow.orthographic) { + u /= tc0_w; + v /= tc0_w; + } + + shadow_z = float24::FromFloat32(std::abs(tc0_w.ToFloat32())); + break; + } default: // TODO: Change to LOG_ERROR when more types are handled. LOG_DEBUG(HW_GPU, "Unhandled texture type %x", (int)texture.config.type); @@ -394,6 +409,22 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve // TODO: Apply the min and mag filters to the texture texture_color[i] = Texture::LookupTexture(texture_data, s, t, info); } + + if (i == 0 && (texture.config.type == TexturingRegs::TextureConfig::Shadow2D || + texture.config.type == TexturingRegs::TextureConfig::ShadowCube)) { + + s32 z_int = static_cast(std::min(shadow_z.ToFloat32(), 1.0f) * 0xFFFFFF); + z_int -= regs.texturing.shadow.bias << 1; + auto& color = texture_color[i]; + s32 z_ref = (color.w << 16) | (color.z << 8) | color.y; + u8 density; + if (z_ref >= z_int) { + density = color.x; + } else { + density = 0; + } + texture_color[i] = {density, density, density, density}; + } } // sample procedural texture @@ -541,6 +572,17 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve } const auto& output_merger = regs.framebuffer.output_merger; + + if (output_merger.fragment_operation_mode == + FramebufferRegs::FragmentOperationMode::Shadow) { + u32 depth_int = static_cast(depth * 0xFFFFFF); + // use green color as the shadow intensity + u8 stencil = combiner_output.y; + DrawShadowMapPixel(x >> 4, y >> 4, depth_int, stencil); + // skip the normal output merger pipeline if it is in shadow mode + continue; + } + // TODO: Does alpha testing happen before or after stencil? if (output_merger.alpha_test.enable) { bool pass = false;