1 // Copyright 2017 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <array>
6 #include <cmath>
7 #include "common/math_util.h"
8 #include "video_core/swrasterizer/proctex.h"
9
10 namespace Pica::Rasterizer {
11
12 using ProcTexClamp = TexturingRegs::ProcTexClamp;
13 using ProcTexShift = TexturingRegs::ProcTexShift;
14 using ProcTexCombiner = TexturingRegs::ProcTexCombiner;
15 using ProcTexFilter = TexturingRegs::ProcTexFilter;
16
LookupLUT(const std::array<State::ProcTex::ValueEntry,128> & lut,float coord)17 static float LookupLUT(const std::array<State::ProcTex::ValueEntry, 128>& lut, float coord) {
18 // For NoiseLUT/ColorMap/AlphaMap, coord=0.0 is lut[0], coord=127.0/128.0 is lut[127] and
19 // coord=1.0 is lut[127]+lut_diff[127]. For other indices, the result is interpolated using
20 // value entries and difference entries.
21 coord *= 128;
22 const int index_int = std::min(static_cast<int>(coord), 127);
23 const float frac = coord - index_int;
24 return lut[index_int].ToFloat() + frac * lut[index_int].DiffToFloat();
25 }
26
27 // These function are used to generate random noise for procedural texture. Their results are
28 // verified against real hardware, but it's not known if the algorithm is the same as hardware.
NoiseRand1D(unsigned int v)29 static unsigned int NoiseRand1D(unsigned int v) {
30 static constexpr std::array<unsigned int, 16> table{
31 {0, 4, 10, 8, 4, 9, 7, 12, 5, 15, 13, 14, 11, 15, 2, 11}};
32 return ((v % 9 + 2) * 3 & 0xF) ^ table[(v / 9) & 0xF];
33 }
34
NoiseRand2D(unsigned int x,unsigned int y)35 static float NoiseRand2D(unsigned int x, unsigned int y) {
36 static constexpr std::array<unsigned int, 16> table{
37 {10, 2, 15, 8, 0, 7, 4, 5, 5, 13, 2, 6, 13, 9, 3, 14}};
38 unsigned int u2 = NoiseRand1D(x);
39 unsigned int v2 = NoiseRand1D(y);
40 v2 += ((u2 & 3) == 1) ? 4 : 0;
41 v2 ^= (u2 & 1) * 6;
42 v2 += 10 + u2;
43 v2 &= 0xF;
44 v2 ^= table[u2];
45 return -1.0f + v2 * 2.0f / 15.0f;
46 }
47
NoiseCoef(float u,float v,const TexturingRegs & regs,const State::ProcTex & state)48 static float NoiseCoef(float u, float v, const TexturingRegs& regs, const State::ProcTex& state) {
49 const float freq_u = float16::FromRaw(regs.proctex_noise_frequency.u).ToFloat32();
50 const float freq_v = float16::FromRaw(regs.proctex_noise_frequency.v).ToFloat32();
51 const float phase_u = float16::FromRaw(regs.proctex_noise_u.phase).ToFloat32();
52 const float phase_v = float16::FromRaw(regs.proctex_noise_v.phase).ToFloat32();
53 const float x = 9 * freq_u * std::abs(u + phase_u);
54 const float y = 9 * freq_v * std::abs(v + phase_v);
55 const int x_int = static_cast<int>(x);
56 const int y_int = static_cast<int>(y);
57 const float x_frac = x - x_int;
58 const float y_frac = y - y_int;
59
60 const float g0 = NoiseRand2D(x_int, y_int) * (x_frac + y_frac);
61 const float g1 = NoiseRand2D(x_int + 1, y_int) * (x_frac + y_frac - 1);
62 const float g2 = NoiseRand2D(x_int, y_int + 1) * (x_frac + y_frac - 1);
63 const float g3 = NoiseRand2D(x_int + 1, y_int + 1) * (x_frac + y_frac - 2);
64 const float x_noise = LookupLUT(state.noise_table, x_frac);
65 const float y_noise = LookupLUT(state.noise_table, y_frac);
66 return Common::BilinearInterp(g0, g1, g2, g3, x_noise, y_noise);
67 }
68
GetShiftOffset(float v,ProcTexShift mode,ProcTexClamp clamp_mode)69 static float GetShiftOffset(float v, ProcTexShift mode, ProcTexClamp clamp_mode) {
70 const float offset = (clamp_mode == ProcTexClamp::MirroredRepeat) ? 1 : 0.5f;
71 switch (mode) {
72 case ProcTexShift::None:
73 return 0;
74 case ProcTexShift::Odd:
75 return offset * (((int)v / 2) % 2);
76 case ProcTexShift::Even:
77 return offset * ((((int)v + 1) / 2) % 2);
78 default:
79 LOG_CRITICAL(HW_GPU, "Unknown shift mode {}", mode);
80 return 0;
81 }
82 };
83
ClampCoord(float & coord,ProcTexClamp mode)84 static void ClampCoord(float& coord, ProcTexClamp mode) {
85 switch (mode) {
86 case ProcTexClamp::ToZero:
87 if (coord > 1.0f)
88 coord = 0.0f;
89 break;
90 case ProcTexClamp::ToEdge:
91 coord = std::min(coord, 1.0f);
92 break;
93 case ProcTexClamp::SymmetricalRepeat:
94 coord = coord - std::floor(coord);
95 break;
96 case ProcTexClamp::MirroredRepeat: {
97 int integer = static_cast<int>(coord);
98 float frac = coord - integer;
99 coord = (integer % 2) == 0 ? frac : (1.0f - frac);
100 break;
101 }
102 case ProcTexClamp::Pulse:
103 if (coord <= 0.5f)
104 coord = 0.0f;
105 else
106 coord = 1.0f;
107 break;
108 default:
109 LOG_CRITICAL(HW_GPU, "Unknown clamp mode {}", mode);
110 coord = std::min(coord, 1.0f);
111 break;
112 }
113 }
114
CombineAndMap(float u,float v,ProcTexCombiner combiner,const std::array<State::ProcTex::ValueEntry,128> & map_table)115 static float CombineAndMap(float u, float v, ProcTexCombiner combiner,
116 const std::array<State::ProcTex::ValueEntry, 128>& map_table) {
117 float f;
118 switch (combiner) {
119 case ProcTexCombiner::U:
120 f = u;
121 break;
122 case ProcTexCombiner::U2:
123 f = u * u;
124 break;
125 case TexturingRegs::ProcTexCombiner::V:
126 f = v;
127 break;
128 case TexturingRegs::ProcTexCombiner::V2:
129 f = v * v;
130 break;
131 case TexturingRegs::ProcTexCombiner::Add:
132 f = (u + v) * 0.5f;
133 break;
134 case TexturingRegs::ProcTexCombiner::Add2:
135 f = (u * u + v * v) * 0.5f;
136 break;
137 case TexturingRegs::ProcTexCombiner::SqrtAdd2:
138 f = std::min(std::sqrt(u * u + v * v), 1.0f);
139 break;
140 case TexturingRegs::ProcTexCombiner::Min:
141 f = std::min(u, v);
142 break;
143 case TexturingRegs::ProcTexCombiner::Max:
144 f = std::max(u, v);
145 break;
146 case TexturingRegs::ProcTexCombiner::RMax:
147 f = std::min(((u + v) * 0.5f + std::sqrt(u * u + v * v)) * 0.5f, 1.0f);
148 break;
149 default:
150 LOG_CRITICAL(HW_GPU, "Unknown combiner {}", combiner);
151 f = 0.0f;
152 break;
153 }
154 return LookupLUT(map_table, f);
155 }
156
ProcTex(float u,float v,const TexturingRegs & regs,const State::ProcTex & state)157 Common::Vec4<u8> ProcTex(float u, float v, const TexturingRegs& regs, const State::ProcTex& state) {
158 u = std::abs(u);
159 v = std::abs(v);
160
161 // Get shift offset before noise generation
162 const float u_shift = GetShiftOffset(v, regs.proctex.u_shift, regs.proctex.u_clamp);
163 const float v_shift = GetShiftOffset(u, regs.proctex.v_shift, regs.proctex.v_clamp);
164
165 // Generate noise
166 if (regs.proctex.noise_enable) {
167 float noise = NoiseCoef(u, v, regs, state);
168 u += noise * regs.proctex_noise_u.amplitude / 4095.0f;
169 v += noise * regs.proctex_noise_v.amplitude / 4095.0f;
170 u = std::abs(u);
171 v = std::abs(v);
172 }
173
174 // Shift
175 u += u_shift;
176 v += v_shift;
177
178 // Clamp
179 ClampCoord(u, regs.proctex.u_clamp);
180 ClampCoord(v, regs.proctex.v_clamp);
181
182 // Combine and map
183 const float lut_coord = CombineAndMap(u, v, regs.proctex.color_combiner, state.color_map_table);
184
185 // Look up the color
186 // For the color lut, coord=0.0 is lut[offset] and coord=1.0 is lut[offset+width-1]
187 const u32 offset = regs.proctex_lut_offset.level0;
188 const u32 width = regs.proctex_lut.width;
189 const float index = offset + (lut_coord * (width - 1));
190 Common::Vec4<u8> final_color;
191 // TODO(wwylele): implement mipmap
192 switch (regs.proctex_lut.filter) {
193 case ProcTexFilter::Linear:
194 case ProcTexFilter::LinearMipmapLinear:
195 case ProcTexFilter::LinearMipmapNearest: {
196 const int index_int = static_cast<int>(index);
197 const float frac = index - index_int;
198 const auto color_value = state.color_table[index_int].ToVector().Cast<float>();
199 const auto color_diff = state.color_diff_table[index_int].ToVector().Cast<float>();
200 final_color = (color_value + frac * color_diff).Cast<u8>();
201 break;
202 }
203 case ProcTexFilter::Nearest:
204 case ProcTexFilter::NearestMipmapLinear:
205 case ProcTexFilter::NearestMipmapNearest:
206 final_color = state.color_table[static_cast<int>(std::round(index))].ToVector();
207 break;
208 }
209
210 if (regs.proctex.separate_alpha) {
211 // Note: in separate alpha mode, the alpha channel skips the color LUT look up stage. It
212 // uses the output of CombineAndMap directly instead.
213 const float final_alpha =
214 CombineAndMap(u, v, regs.proctex.alpha_combiner, state.alpha_map_table);
215 return Common::MakeVec<u8>(final_color.rgb(), static_cast<u8>(final_alpha * 255));
216 } else {
217 return final_color;
218 }
219 }
220
221 } // namespace Pica::Rasterizer
222