Commit | Line | Data |
---|---|---|
3719602c PC |
1 | /* Copyright (C) 2010-2020 The RetroArch team |
2 | * | |
3 | * --------------------------------------------------------------------------------------- | |
4 | * The following license statement only applies to this file (scaler_filter.c). | |
5 | * --------------------------------------------------------------------------------------- | |
6 | * | |
7 | * Permission is hereby granted, free of charge, | |
8 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), | |
9 | * to deal in the Software without restriction, including without limitation the rights to | |
10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, | |
11 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | */ | |
22 | ||
23 | #include <stdio.h> | |
24 | #include <string.h> | |
25 | ||
26 | #include <gfx/scaler/filter.h> | |
27 | #include <gfx/scaler/scaler_int.h> | |
28 | #include <retro_inline.h> | |
29 | #include <filters.h> | |
30 | #include <retro_math.h> | |
31 | ||
32 | #define FILTER_UNITY (1 << 14) | |
33 | ||
34 | static INLINE void gen_filter_point_sub(struct scaler_filter *filter, | |
35 | int len, int pos, int step) | |
36 | { | |
37 | int i; | |
38 | for (i = 0; i < len; i++, pos += step) | |
39 | { | |
40 | filter->filter_pos[i] = pos >> 16; | |
41 | filter->filter[i] = FILTER_UNITY; | |
42 | } | |
43 | } | |
44 | ||
45 | static INLINE void gen_filter_bilinear_sub(struct scaler_filter *filter, | |
46 | int len, int pos, int step) | |
47 | { | |
48 | int i; | |
49 | for (i = 0; i < len; i++, pos += step) | |
50 | { | |
51 | filter->filter_pos[i] = pos >> 16; | |
52 | filter->filter[i * 2 + 1] = (pos & 0xffff) >> 2; | |
53 | filter->filter[i * 2 + 0] = FILTER_UNITY - filter->filter[i * 2 + 1]; | |
54 | } | |
55 | } | |
56 | ||
57 | static INLINE void gen_filter_sinc_sub(struct scaler_filter *filter, | |
58 | int len, int pos, int step, double phase_mul) | |
59 | { | |
60 | int i, j; | |
61 | const int sinc_size = filter->filter_len; | |
62 | ||
63 | for (i = 0; i < len; i++, pos += step) | |
64 | { | |
65 | filter->filter_pos[i] = pos >> 16; | |
66 | ||
67 | for (j = 0; j < sinc_size; j++) | |
68 | { | |
69 | double sinc_phase = M_PI * ((double)((sinc_size << 15) + (pos & 0xffff)) / 0x10000 - j); | |
70 | double lanczos_phase = sinc_phase / ((sinc_size >> 1)); | |
71 | int16_t sinc_val = FILTER_UNITY * sinc(sinc_phase * phase_mul) * sinc(lanczos_phase) * phase_mul; | |
72 | ||
73 | filter->filter[i * sinc_size + j] = sinc_val; | |
74 | } | |
75 | } | |
76 | } | |
77 | ||
78 | static bool validate_filter(struct scaler_ctx *ctx) | |
79 | { | |
80 | int i; | |
81 | int max_h_pos; | |
82 | int max_w_pos = ctx->in_width - ctx->horiz.filter_len; | |
83 | ||
84 | for (i = 0; i < ctx->out_width; i++) | |
85 | { | |
86 | if (ctx->horiz.filter_pos[i] > max_w_pos || ctx->horiz.filter_pos[i] < 0) | |
87 | { | |
88 | #ifndef NDEBUG | |
89 | fprintf(stderr, "Out X = %d => In X = %d\n", i, ctx->horiz.filter_pos[i]); | |
90 | #endif | |
91 | return false; | |
92 | } | |
93 | } | |
94 | ||
95 | max_h_pos = ctx->in_height - ctx->vert.filter_len; | |
96 | ||
97 | for (i = 0; i < ctx->out_height; i++) | |
98 | { | |
99 | if (ctx->vert.filter_pos[i] > max_h_pos || ctx->vert.filter_pos[i] < 0) | |
100 | { | |
101 | #ifndef NDEBUG | |
102 | fprintf(stderr, "Out Y = %d => In Y = %d\n", i, ctx->vert.filter_pos[i]); | |
103 | #endif | |
104 | return false; | |
105 | } | |
106 | } | |
107 | ||
108 | return true; | |
109 | } | |
110 | ||
111 | static void fixup_filter_sub(struct scaler_filter *filter, | |
112 | int out_len, int in_len) | |
113 | { | |
114 | int i; | |
115 | int max_pos = in_len - filter->filter_len; | |
116 | ||
117 | for (i = 0; i < out_len; i++) | |
118 | { | |
119 | int postsample = filter->filter_pos[i] - max_pos; | |
120 | int presample = -filter->filter_pos[i]; | |
121 | ||
122 | if (postsample > 0) | |
123 | { | |
124 | int16_t *base_filter = NULL; | |
125 | ||
126 | filter->filter_pos[i] -= postsample; | |
127 | ||
128 | base_filter = filter->filter + i * filter->filter_stride; | |
129 | ||
130 | if (postsample > (int)filter->filter_len) | |
131 | memset(base_filter, 0, filter->filter_len * sizeof(int16_t)); | |
132 | else | |
133 | { | |
134 | memmove(base_filter + postsample, base_filter, | |
135 | (filter->filter_len - postsample) * sizeof(int16_t)); | |
136 | memset(base_filter, 0, postsample * sizeof(int16_t)); | |
137 | } | |
138 | } | |
139 | ||
140 | if (presample > 0) | |
141 | { | |
142 | int16_t *base_filter = NULL; | |
143 | ||
144 | filter->filter_pos[i] += presample; | |
145 | base_filter = filter->filter + i * filter->filter_stride; | |
146 | ||
147 | if (presample > (int)filter->filter_len) | |
148 | memset(base_filter, 0, filter->filter_len * sizeof(int16_t)); | |
149 | else | |
150 | { | |
151 | memmove(base_filter, base_filter + presample, | |
152 | (filter->filter_len - presample) * sizeof(int16_t)); | |
153 | memset(base_filter + (filter->filter_len - presample), | |
154 | 0, presample * sizeof(int16_t)); | |
155 | } | |
156 | } | |
157 | } | |
158 | } | |
159 | ||
160 | bool scaler_gen_filter(struct scaler_ctx *ctx) | |
161 | { | |
162 | int x_pos, x_step, y_pos, y_step; | |
163 | int sinc_size = 0; | |
164 | ||
165 | switch (ctx->scaler_type) | |
166 | { | |
167 | case SCALER_TYPE_POINT: | |
168 | ctx->horiz.filter_len = 1; | |
169 | ctx->horiz.filter_stride = 1; | |
170 | ctx->vert.filter_len = 1; | |
171 | ctx->vert.filter_stride = 1; | |
172 | break; | |
173 | case SCALER_TYPE_BILINEAR: | |
174 | ctx->horiz.filter_len = 2; | |
175 | ctx->horiz.filter_stride = 2; | |
176 | ctx->vert.filter_len = 2; | |
177 | ctx->vert.filter_stride = 2; | |
178 | break; | |
179 | case SCALER_TYPE_SINC: | |
180 | sinc_size = 8 * ((ctx->in_width > ctx->out_width) | |
181 | ? next_pow2(ctx->in_width / ctx->out_width) : 1); | |
182 | ctx->horiz.filter_len = sinc_size; | |
183 | ctx->horiz.filter_stride = sinc_size; | |
184 | ctx->vert.filter_len = sinc_size; | |
185 | ctx->vert.filter_stride = sinc_size; | |
186 | break; | |
187 | case SCALER_TYPE_UNKNOWN: | |
188 | default: | |
189 | return false; | |
190 | } | |
191 | ||
192 | ctx->horiz.filter = (int16_t*)calloc(sizeof(int16_t), ctx->horiz.filter_stride * ctx->out_width); | |
193 | ctx->horiz.filter_pos = (int*)calloc(sizeof(int), ctx->out_width); | |
194 | ||
195 | ctx->vert.filter = (int16_t*)calloc(sizeof(int16_t), ctx->vert.filter_stride * ctx->out_height); | |
196 | ctx->vert.filter_pos = (int*)calloc(sizeof(int), ctx->out_height); | |
197 | ||
198 | if (!ctx->horiz.filter || !ctx->vert.filter) | |
199 | return false; | |
200 | ||
201 | x_step = (1 << 16) * ctx->in_width / ctx->out_width; | |
202 | y_step = (1 << 16) * ctx->in_height / ctx->out_height; | |
203 | ||
204 | switch (ctx->scaler_type) | |
205 | { | |
206 | case SCALER_TYPE_POINT: | |
207 | x_pos = (1 << 15) * ctx->in_width / ctx->out_width - (1 << 15); | |
208 | y_pos = (1 << 15) * ctx->in_height / ctx->out_height - (1 << 15); | |
209 | ||
210 | gen_filter_point_sub(&ctx->horiz, ctx->out_width, x_pos, x_step); | |
211 | gen_filter_point_sub(&ctx->vert, ctx->out_height, y_pos, y_step); | |
212 | ||
213 | ctx->scaler_special = scaler_argb8888_point_special; | |
214 | break; | |
215 | ||
216 | case SCALER_TYPE_BILINEAR: | |
217 | x_pos = (1 << 15) * ctx->in_width / ctx->out_width - (1 << 15); | |
218 | y_pos = (1 << 15) * ctx->in_height / ctx->out_height - (1 << 15); | |
219 | ||
220 | gen_filter_bilinear_sub(&ctx->horiz, ctx->out_width, x_pos, x_step); | |
221 | gen_filter_bilinear_sub(&ctx->vert, ctx->out_height, y_pos, y_step); | |
222 | break; | |
223 | ||
224 | case SCALER_TYPE_SINC: | |
225 | /* Need to expand the filter when downsampling | |
226 | * to get a proper low-pass effect. */ | |
227 | ||
228 | x_pos = (1 << 15) * ctx->in_width / ctx->out_width - (1 << 15) - (sinc_size << 15); | |
229 | y_pos = (1 << 15) * ctx->in_height / ctx->out_height - (1 << 15) - (sinc_size << 15); | |
230 | ||
231 | gen_filter_sinc_sub(&ctx->horiz, ctx->out_width, x_pos, x_step, | |
232 | ctx->in_width > ctx->out_width ? (double)ctx->out_width / ctx->in_width : 1.0); | |
233 | gen_filter_sinc_sub(&ctx->vert, ctx->out_height, y_pos, y_step, | |
234 | ctx->in_height > ctx->out_height ? (double)ctx->out_height / ctx->in_height : 1.0 | |
235 | ); | |
236 | break; | |
237 | case SCALER_TYPE_UNKNOWN: | |
238 | break; | |
239 | } | |
240 | ||
241 | /* Makes sure that we never sample outside our rectangle. */ | |
242 | fixup_filter_sub(&ctx->horiz, ctx->out_width, ctx->in_width); | |
243 | fixup_filter_sub(&ctx->vert, ctx->out_height, ctx->in_height); | |
244 | ||
245 | return validate_filter(ctx); | |
246 | } |