diff options
Diffstat (limited to 'src/video_core')
-rw-r--r-- | src/video_core/regs_lighting.h | 63 | ||||
-rw-r--r-- | src/video_core/regs_texturing.h | 27 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 64 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.h | 6 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.cpp | 39 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.h | 3 | ||||
-rw-r--r-- | src/video_core/swrasterizer/rasterizer.cpp | 53 |
7 files changed, 216 insertions, 39 deletions
diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h index 6793405d9..fbfebc0a7 100644 --- a/src/video_core/regs_lighting.h +++ b/src/video_core/regs_lighting.h @@ -26,6 +26,16 @@ struct LightingRegs { DistanceAttenuation = 16, }; + static LightingSampler SpotlightAttenuationSampler(unsigned index) { + return static_cast<LightingSampler>( + static_cast<unsigned>(LightingSampler::SpotlightAttenuation) + index); + } + + static LightingSampler DistanceAttenuationSampler(unsigned index) { + return static_cast<LightingSampler>( + static_cast<unsigned>(LightingSampler::DistanceAttenuation) + index); + } + /** * Pica fragment lighting supports using different LUTs for each lighting component: Reflectance * R, G, and B channels, distribution function for specular components 0 and 1, fresnel factor, @@ -73,6 +83,8 @@ struct LightingRegs { VH = 1, // Cosine of the angle between the view and half-angle vectors NV = 2, // Cosine of the angle between the normal and the view vector LN = 3, // Cosine of the angle between the light and the normal vectors + SP = 4, // Cosine of the angle between the light and the inverse spotlight vectors + CP = 5, // TODO: document and implement }; enum class LightingBumpMode : u32 { @@ -104,6 +116,9 @@ struct LightingRegs { return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) && (config != LightingConfig::Config5); + case LightingSampler::SpotlightAttenuation: + return (config != LightingConfig::Config2) && (config != LightingConfig::Config3); + case LightingSampler::Fresnel: return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) && (config != LightingConfig::Config4); @@ -116,11 +131,10 @@ struct LightingRegs { return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || (config == LightingConfig::Config7); default: - UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " - "unreachable section, sampler should be one " - "of Distribution0, Distribution1, Fresnel, " - "ReflectRed, ReflectGreen or ReflectBlue, instead " - "got %i", + UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached unreachable section, " + "sampler should be one of Distribution0, Distribution1, " + "SpotlightAttenuation, Fresnel, ReflectRed, ReflectGreen or " + "ReflectBlue, instead got %i", static_cast<int>(config)); } } @@ -140,7 +154,16 @@ struct LightingRegs { BitField<0, 16, u32> z; }; - INSERT_PADDING_WORDS(0x3); + // inverse spotlight direction vector, encoded as fixed1.1.11 + union { + BitField<0, 13, s32> spot_x; + BitField<16, 13, s32> spot_y; + }; + union { + BitField<0, 13, s32> spot_z; + }; + + INSERT_PADDING_WORDS(0x1); union { BitField<0, 1, u32> directional; @@ -169,8 +192,16 @@ struct LightingRegs { } config0; union { + u32 raw; + + // Each bit specifies whether spot light attenuation should be applied for the corresponding + // light. + BitField<8, 8, u32> disable_spot_atten; + BitField<16, 1, u32> disable_lut_d0; BitField<17, 1, u32> disable_lut_d1; + // Note: by intuition, BitField<18, 1, u32> should be disable_lut_sp, but it is actually a + // dummy bit which is always set as 1. BitField<19, 1, u32> disable_lut_fr; BitField<20, 1, u32> disable_lut_rr; BitField<21, 1, u32> disable_lut_rg; @@ -178,23 +209,15 @@ struct LightingRegs { // Each bit specifies whether distance attenuation should be applied for the corresponding // light. - BitField<24, 1, u32> disable_dist_atten_light_0; - BitField<25, 1, u32> disable_dist_atten_light_1; - BitField<26, 1, u32> disable_dist_atten_light_2; - BitField<27, 1, u32> disable_dist_atten_light_3; - BitField<28, 1, u32> disable_dist_atten_light_4; - BitField<29, 1, u32> disable_dist_atten_light_5; - BitField<30, 1, u32> disable_dist_atten_light_6; - BitField<31, 1, u32> disable_dist_atten_light_7; + BitField<24, 8, u32> disable_dist_atten; } config1; bool IsDistAttenDisabled(unsigned index) const { - const unsigned disable[] = { - config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, - config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, - config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, - config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7}; - return disable[index] != 0; + return (config1.disable_dist_atten & (1 << index)) != 0; + } + + bool IsSpotAttenDisabled(unsigned index) const { + return (config1.disable_spot_atten & (1 << index)) != 0; } union { diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index 2c654d5c8..0b09f2299 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -133,7 +133,32 @@ struct TexturingRegs { BitField<16, 1, u32> clear_texture_cache; // TODO: unimplemented } main_config; TextureConfig texture0; - INSERT_PADDING_WORDS(0x8); + + enum class CubeFace { + PositiveX = 0, + NegativeX = 1, + PositiveY = 2, + NegativeY = 3, + PositiveZ = 4, + NegativeZ = 5, + }; + + BitField<0, 22, u32> cube_address[5]; + + PAddr GetCubePhysicalAddress(CubeFace face) const { + PAddr address = texture0.address; + if (face != CubeFace::PositiveX) { + // Bits [22:27] from the main texture address is shared with all cubemap additional + // addresses. + auto& face_addr = cube_address[static_cast<size_t>(face) - 1]; + address &= ~face_addr.mask; + address |= face_addr; + } + // A multiplier of 8 is also needed in the same way as the main address. + return address * 8; + } + + INSERT_PADDING_WORDS(0x3); BitField<0, 4, TextureFormat> texture0_format; BitField<0, 1, u32> fragment_lighting_enable; INSERT_PADDING_WORDS(0x1); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index aa9b831dd..e6cccebf6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -182,19 +182,22 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { RasterizerOpenGL::~RasterizerOpenGL() {} /** - * This is a helper function to resolve an issue with opposite quaternions being interpolated by - * OpenGL. See below for a detailed description of this issue (yuriks): + * This is a helper function to resolve an issue when interpolating opposite quaternions. See below + * for a detailed description of this issue (yuriks): * * For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you * interpolate two quaternions that are opposite, instead of going from one rotation to another * using the shortest path, you'll go around the longest path. You can test if two quaternions are - * opposite by checking if Dot(Q1, W2) < 0. In that case, you can flip either of them, therefore - * making Dot(-Q1, W2) positive. + * opposite by checking if Dot(Q1, Q2) < 0. In that case, you can flip either of them, therefore + * making Dot(Q1, -Q2) positive. * - * NOTE: This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This - * should be correct for nearly all cases, however a more correct implementation (but less trivial - * and perhaps unnecessary) would be to handle this per-fragment, by interpolating the quaternions - * manually using two Lerps, and doing this correction before each Lerp. + * This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This is + * correct for most cases but can still rotate around the long way sometimes. An implementation + * which did `lerp(lerp(Q1, Q2), Q3)` (with proper weighting), applying the dot product check + * between each step would work for those cases at the cost of being more complex to implement. + * + * Fortunately however, the 3DS hardware happens to also use this exact same logic to work around + * these issues, making this basic implementation actually more accurate to the hardware. */ static bool AreQuaternionsOpposite(Math::Vec4<Pica::float24> qa, Math::Vec4<Pica::float24> qb) { Math::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()}; @@ -735,6 +738,40 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncLightPosition(7); break; + // Fragment spot lighting direction + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].spot_x, 0x146 + 0 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].spot_z, 0x147 + 0 * 0x10): + SyncLightSpotDirection(0); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].spot_x, 0x146 + 1 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].spot_z, 0x147 + 1 * 0x10): + SyncLightSpotDirection(1); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].spot_x, 0x146 + 2 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].spot_z, 0x147 + 2 * 0x10): + SyncLightSpotDirection(2); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].spot_x, 0x146 + 3 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].spot_z, 0x147 + 3 * 0x10): + SyncLightSpotDirection(3); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].spot_x, 0x146 + 4 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].spot_z, 0x147 + 4 * 0x10): + SyncLightSpotDirection(4); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].spot_x, 0x146 + 5 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].spot_z, 0x147 + 5 * 0x10): + SyncLightSpotDirection(5); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].spot_x, 0x146 + 6 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].spot_z, 0x147 + 6 * 0x10): + SyncLightSpotDirection(6); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].spot_x, 0x146 + 7 * 0x10): + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].spot_z, 0x147 + 7 * 0x10): + SyncLightSpotDirection(7); + break; + // Fragment lighting light source config case PICA_REG_INDEX_WORKAROUND(lighting.light[0].config, 0x149 + 0 * 0x10): case PICA_REG_INDEX_WORKAROUND(lighting.light[1].config, 0x149 + 1 * 0x10): @@ -1595,6 +1632,17 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) { } } +void RasterizerOpenGL::SyncLightSpotDirection(int light_index) { + const auto& light = Pica::g_state.regs.lighting.light[light_index]; + GLvec3 spot_direction = {light.spot_x / 2047.0f, light.spot_y / 2047.0f, + light.spot_z / 2047.0f}; + + if (spot_direction != uniform_block_data.data.light_src[light_index].spot_direction) { + uniform_block_data.data.light_src[light_index].spot_direction = spot_direction; + uniform_block_data.dirty = true; + } +} + void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) { GLfloat dist_atten_bias = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index a9ad7d660..d9a3e9d1c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -125,6 +125,7 @@ private: alignas(16) GLvec3 diffuse; alignas(16) GLvec3 ambient; alignas(16) GLvec3 position; + alignas(16) GLvec3 spot_direction; // negated GLfloat dist_atten_bias; GLfloat dist_atten_scale; }; @@ -153,7 +154,7 @@ private: }; static_assert( - sizeof(UniformData) == 0x3E0, + sizeof(UniformData) == 0x460, "The size of the UniformData structure has changed, update the structure in the shader"); static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec"); @@ -241,6 +242,9 @@ private: /// Syncs the specified light's position to match the PICA register void SyncLightPosition(int light_index); + /// Syncs the specified spot light direcition to match the PICA register + void SyncLightSpotDirection(int light_index); + /// Syncs the specified light's distance attenuation bias to match the PICA register void SyncLightDistanceAttenuationBias(int light_index); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index ffe419863..db53710aa 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -75,6 +75,8 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0; state.lighting.light[light_index].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num); + state.lighting.light[light_index].spot_atten_enable = + !regs.lighting.IsSpotAttenDisabled(num); } state.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0; @@ -87,6 +89,12 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { state.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value(); state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1); + // this is a dummy field due to lack of the corresponding register + state.lighting.lut_sp.enable = true; + state.lighting.lut_sp.abs_input = regs.lighting.abs_lut_input.disable_sp == 0; + state.lighting.lut_sp.type = regs.lighting.lut_input.sp.Value(); + state.lighting.lut_sp.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.sp); + state.lighting.lut_fr.enable = regs.lighting.config1.disable_lut_fr == 0; state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0; state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value(); @@ -509,7 +517,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { out += "vec4 diffuse_sum = vec4(0.0, 0.0, 0.0, 1.0);\n" "vec4 specular_sum = vec4(0.0, 0.0, 0.0, 1.0);\n" "vec3 light_vector = vec3(0.0);\n" - "vec3 refl_value = vec3(0.0);\n"; + "vec3 refl_value = vec3(0.0);\n" + "vec3 spot_dir = vec3(0.0);\n;"; // Compute fragment normals if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { @@ -560,6 +569,10 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { index = std::string("dot(light_vector, normal)"); break; + case LightingRegs::LightingLutInput::SP: + index = std::string("dot(light_vector, spot_dir)"); + break; + default: LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %d\n", (int)input); UNIMPLEMENTED(); @@ -596,21 +609,34 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { else out += "light_vector = normalize(" + light_src + ".position + view);\n"; + out += "spot_dir = " + light_src + ".spot_direction;\n"; + // Compute dot product of light_vector and normal, adjust if lighting is one-sided or // two-sided std::string dot_product = light_config.two_sided_diffuse ? "abs(dot(light_vector, normal))" : "max(dot(light_vector, normal), 0.0)"; + // If enabled, compute spot light attenuation value + std::string spot_atten = "1.0"; + if (light_config.spot_atten_enable && + LightingRegs::IsLightingSamplerSupported( + lighting.config, LightingRegs::LightingSampler::SpotlightAttenuation)) { + std::string index = + GetLutIndex(light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input); + auto sampler = LightingRegs::SpotlightAttenuationSampler(light_config.num); + spot_atten = "(" + std::to_string(lighting.lut_sp.scale) + " * " + + GetLutValue(sampler, index) + ")"; + } + // If enabled, compute distance attenuation value std::string dist_atten = "1.0"; if (light_config.dist_atten_enable) { std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " + light_src + ".position) + " + light_src + ".dist_atten_bias)"; index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))"; - const unsigned lut_num = - ((unsigned)LightingRegs::LightingSampler::DistanceAttenuation + light_config.num); - dist_atten = GetLutValue((LightingRegs::LightingSampler)lut_num, index); + auto sampler = LightingRegs::DistanceAttenuationSampler(light_config.num); + dist_atten = GetLutValue(sampler, index); } // If enabled, clamp specular component if lighting result is negative @@ -711,11 +737,11 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Compute primary fragment color (diffuse lighting) function out += "diffuse_sum.rgb += ((" + light_src + ".diffuse * " + dot_product + ") + " + - light_src + ".ambient) * " + dist_atten + ";\n"; + light_src + ".ambient) * " + dist_atten + " * " + spot_atten + ";\n"; // Compute secondary fragment color (specular lighting) function out += "specular_sum.rgb += (" + specular_0 + " + " + specular_1 + ") * " + - clamp_highlights + " * " + dist_atten + ";\n"; + clamp_highlights + " * " + dist_atten + " * " + spot_atten + ";\n"; } // Sum final lighting result @@ -967,6 +993,7 @@ struct LightSrc { vec3 diffuse; vec3 ambient; vec3 position; + vec3 spot_direction; float dist_atten_bias; float dist_atten_scale; }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index ea6d216d1..9c90eadf9 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -93,6 +93,7 @@ union PicaShaderConfig { bool directional; bool two_sided_diffuse; bool dist_atten_enable; + bool spot_atten_enable; } light[8]; bool enable; @@ -110,7 +111,7 @@ union PicaShaderConfig { bool abs_input; Pica::LightingRegs::LightingLutInput type; float scale; - } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; + } lut_d0, lut_d1, lut_sp, lut_fr, lut_rr, lut_rg, lut_rb; } lighting; struct { diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index 908c7bb9e..cd7b6c39d 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <array> #include <cmath> +#include <tuple> #include "common/assert.h" #include "common/bit_field.h" #include "common/color.h" @@ -70,6 +71,49 @@ static int SignedArea(const Math::Vec2<Fix12P4>& vtx1, const Math::Vec2<Fix12P4> return Math::Cross(vec1, vec2).z; }; +/// Convert a 3D vector for cube map coordinates to 2D texture coordinates along with the face name +static std::tuple<float24, float24, PAddr> 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()); + float24 x, y, z; + PAddr addr; + if (abs_u > abs_v && abs_u > abs_w) { + if (u > float24::FromFloat32(0)) { + addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveX); + y = -v; + } else { + addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeX); + y = v; + } + x = -w; + z = u; + } else if (abs_v > abs_w) { + if (v > float24::FromFloat32(0)) { + addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveY); + x = u; + } else { + addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeY); + x = -u; + } + y = w; + z = v; + } else { + if (w > float24::FromFloat32(0)) { + addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveZ); + y = -v; + } else { + addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeZ); + y = v; + } + x = u; + z = w; + } + const float24 half = float24::FromFloat32(0.5f); + return std::make_tuple(x / z * half + half, y / z * half + half, addr); +} + MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240)); /** @@ -284,10 +328,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(); if (i == 0) { switch (texture.config.type) { case TexturingRegs::TextureConfig::Texture2D: break; + 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); + break; + } case TexturingRegs::TextureConfig::Projection2D: { auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); u /= tc0_w; @@ -334,8 +384,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve t = texture.config.height - 1 - GetWrappedTexCoord(texture.config.wrap_t, t, texture.config.height); - u8* texture_data = - Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); + const u8* texture_data = Memory::GetPhysicalPointer(texture_address); auto info = Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); |