standalone: fix w/h confusion
[pcsx_rearmed.git] / deps / libretro-common / gfx / scaler / scaler_filter.c
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 }