diff --git a/demos/sdl2.c b/demos/sdl2.c index ac97c5ee080fac4978c1a7ed74bd60c0ab54bafb..8039b2e0e20c9c3c4835fa59af95a62586ac8c4c 100644 --- a/demos/sdl2.c +++ b/demos/sdl2.c @@ -115,10 +115,12 @@ static void init_sdl() { exit(1); } + uint32_t window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | + SDL_WINDOW_VULKAN; + window = SDL_CreateWindow("libplacebo demo", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - WINDOW_WIDTH, WINDOW_HEIGHT, - SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN); + WINDOW_WIDTH, WINDOW_HEIGHT, window_flags); if (!window) { fprintf(stderr, "Failed creating window: %s\n", SDL_GetError()); @@ -269,8 +271,7 @@ static void render_frame(const struct pl_swapchain_frame *frame) .planes = { img_plane }, .repr = pl_color_repr_unknown, .color = pl_color_space_unknown, - .width = img->params.w, - .height = img->params.h, + .src_rect = {0, 0, img->params.w, img->params.h}, }; // This seems to be the case for SDL2_image @@ -283,6 +284,8 @@ static void render_frame(const struct pl_swapchain_frame *frame) .len = icc_profile.size, }; + pl_rect2d_aspect_copy(&target.dst_rect, &image.src_rect, 0.0); + const struct pl_tex *osd = osd_plane.texture; struct pl_overlay target_ol; if (osd) { @@ -297,6 +300,9 @@ static void render_frame(const struct pl_swapchain_frame *frame) target.num_overlays = 1; } + if (pl_render_target_partial(&target)) + pl_tex_clear(vk->gpu, target.fbo, (float[4]) {0} ); + // Use the heaviest preset purely for demonstration/testing purposes if (!pl_render_image(renderer, &image, &target, &pl_render_high_quality_params)) { fprintf(stderr, "Failed rendering frame!\n"); diff --git a/meson.build b/meson.build index e7222ced5dfc8250cde9e231fb367134333b74f4..46cf6d058820236d8ff29c98706be5760b44ec6d 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('libplacebo', ['c', 'cpp'], license: 'LGPL2.1+', default_options: ['c_std=c99', 'cpp_std=c++11', 'warning_level=2'], meson_version: '>=0.49', - version: '2.63.0', + version: '2.64.0', ) # Version number diff --git a/src/common.c b/src/common.c index 57b3cf08b676a64dd42023c5d37cf3f12fc88052..5cbabfc20e35991716862fd21f4b65784ee033b1 100644 --- a/src/common.c +++ b/src/common.c @@ -15,6 +15,8 @@ * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>. */ +#include <math.h> + #include "common.h" void pl_rect2d_normalize(struct pl_rect2d *rc) @@ -39,6 +41,28 @@ void pl_rect3d_normalize(struct pl_rect3d *rc) }; } +struct pl_rect2d pl_rect2df_round(const struct pl_rect2df *rc) +{ + return (struct pl_rect2d) { + .x0 = roundf(rc->x0), + .x1 = roundf(rc->x1), + .y0 = roundf(rc->y0), + .y1 = roundf(rc->y1), + }; +} + +struct pl_rect3d pl_rect3df_round(const struct pl_rect3df *rc) +{ + return (struct pl_rect3d) { + .x0 = roundf(rc->x0), + .x1 = roundf(rc->x1), + .y0 = roundf(rc->y0), + .y1 = roundf(rc->y1), + .z0 = roundf(rc->z0), + .z1 = roundf(rc->z1), + }; +} + const struct pl_matrix3x3 pl_matrix3x3_identity = {{ { 1, 0, 0 }, { 0, 1, 0 }, @@ -222,3 +246,119 @@ void pl_transform2x2_apply_rc(const struct pl_transform2x2 *t, struct pl_rect2df rc->y0 += t->c[1]; rc->y1 += t->c[1]; } + +float pl_rect2df_aspect(const struct pl_rect2df *rc) +{ + float w = fabs(pl_rect_w(*rc)), h = fabs(pl_rect_h(*rc)); + return h ? (w / h) : 0.0; +} + +void pl_rect2df_aspect_set(struct pl_rect2df *rc, float aspect, float panscan) +{ + pl_assert(aspect >= 0); + float orig_aspect = pl_rect2df_aspect(rc); + if (!aspect || !orig_aspect) + return; + + float scale_x, scale_y; + if (aspect > orig_aspect) { + // New aspect is wider than the original, so we need to either grow in + // scale_x (panscan=1) or shrink in scale_y (panscan=0) + scale_x = powf(aspect / orig_aspect, panscan); + scale_y = powf(aspect / orig_aspect, panscan - 1.0); + } else if (aspect < orig_aspect) { + // New aspect is taller, so either grow in scale_y (panscan=1) or + // shrink in scale_x (panscan=0) + scale_x = powf(orig_aspect / aspect, panscan - 1.0); + scale_y = powf(orig_aspect / aspect, panscan); + } else { + return; // No change in aspect + } + + pl_rect2df_stretch(rc, scale_x, scale_y); +} + +void pl_rect2df_aspect_fit(struct pl_rect2df *rc, const struct pl_rect2df *src, + float panscan) +{ + float orig_w = fabs(pl_rect_w(*rc)), + orig_h = fabs(pl_rect_h(*rc)); + if (!orig_w || !orig_h) + return; + + // If either one of these is larger than 1, then we need to shrink to fit, + // otherwise we can just directly stretch the rect. + float scale_x = fabs(pl_rect_w(*src)) / orig_w, + scale_y = fabs(pl_rect_h(*src)) / orig_h; + + if (scale_x > 1.0 || scale_y > 1.0) { + pl_rect2df_aspect_copy(rc, src, panscan); + } else { + pl_rect2df_stretch(rc, scale_x, scale_y); + } +} + +void pl_rect2df_stretch(struct pl_rect2df *rc, float stretch_x, float stretch_y) +{ + float midx = (rc->x0 + rc->x1) / 2.0, + midy = (rc->y0 + rc->y1) / 2.0; + + rc->x0 = rc->x0 * stretch_x + midx * (1.0 - stretch_x); + rc->x1 = rc->x1 * stretch_x + midx * (1.0 - stretch_x); + rc->y0 = rc->y0 * stretch_y + midy * (1.0 - stretch_y); + rc->y1 = rc->y1 * stretch_y + midy * (1.0 - stretch_y); +} + +void pl_rect2df_offset(struct pl_rect2df *rc, float offset_x, float offset_y) +{ + if (rc->x1 < rc->x0) + offset_x = -offset_x; + if (rc->y1 < rc->y0) + offset_y = -offset_y; + + rc->x0 += offset_x; + rc->x1 += offset_x; + rc->y0 += offset_y; + rc->y1 += offset_y; +} + +float pl_rect2d_aspect(const struct pl_rect2d *rc) +{ + float w = abs(pl_rect_w(*rc)), h = abs(pl_rect_h(*rc)); + return h ? (w / h) : 0.0; +} + +void pl_rect2d_aspect_set(struct pl_rect2d *rc, float aspect, float panscan) +{ + struct pl_rect2df frc = { rc->x0, rc->y0, rc->x1, rc->y1 }; + pl_rect2df_aspect_set(&frc, aspect, panscan); + *rc = pl_rect2df_round(&frc); +} + +void pl_rect2d_aspect_fit(struct pl_rect2d *rc, const struct pl_rect2df *src, + float panscan) +{ + struct pl_rect2df frc = { rc->x0, rc->y0, rc->x1, rc->y1 }; + pl_rect2df_aspect_fit(&frc, src, panscan); + *rc = pl_rect2df_round(&frc); +} + +void pl_rect2d_stretch(struct pl_rect2d *rc, float stretch_x, float stretch_y) +{ + struct pl_rect2df frc = { rc->x0, rc->y0, rc->x1, rc->y1 }; + pl_rect2df_stretch(&frc, stretch_x, stretch_y); + *rc = pl_rect2df_round(&frc); +} + +void pl_rect2d_offset(struct pl_rect2d *rc, int offset_x, int offset_y) +{ + if (rc->x1 < rc->x0) + offset_x = -offset_x; + if (rc->y1 < rc->y0) + offset_y = -offset_y; + + rc->x0 += offset_x; + rc->x1 += offset_x; + rc->y0 += offset_y; + rc->y1 += offset_y; +} diff --git a/src/include/libplacebo/common.h b/src/include/libplacebo/common.h index 8b5cb4ae3d785c612596636b7d318706f231c9f0..8d8a79c44c10f90bce287450ded78b44d89aead6 100644 --- a/src/include/libplacebo/common.h +++ b/src/include/libplacebo/common.h @@ -62,6 +62,10 @@ struct pl_rect3df { void pl_rect2d_normalize(struct pl_rect2d *rc); void pl_rect3d_normalize(struct pl_rect3d *rc); +// Return the rounded form of a rect. +struct pl_rect2d pl_rect2df_round(const struct pl_rect2df *rc); +struct pl_rect3d pl_rect3df_round(const struct pl_rect3df *rc); + // Represents a row-major matrix, i.e. the following matrix // [ a11 a12 a13 ] // [ a21 a22 a23 ] @@ -136,4 +140,49 @@ extern const struct pl_transform2x2 pl_transform2x2_identity; void pl_transform2x2_apply(const struct pl_transform2x2 *t, float vec[2]); void pl_transform2x2_apply_rc(const struct pl_transform2x2 *t, struct pl_rect2df *rc); +// Helper functions for dealing with aspect ratios and stretched/scaled rects. + +// Return the (absolute) aspect ratio (width/height) of a given pl_rect2df. +// This will always be a positive number, even if `rc` is flipped. +float pl_rect2df_aspect(const struct pl_rect2df *rc); + +// Set the aspect of a `rc` to a given aspect ratio with an extra 'panscan' +// factor choosing the balance between shrinking and growing the `rc` to meet +// this aspect ratio. If `panscan` is 0.0, this function will only ever shrink +// the rc . If `panscan` is 1.0, this function will only ever grow the `rc`. +void pl_rect2df_aspect_set(struct pl_rect2df *rc, float aspect, float panscan); + +// Set one rect's aspect to that of another +#define pl_rect2df_aspect_copy(rc, src, panscan) \ + pl_rect2df_aspect_set((rc), pl_rect2df_aspect(src), (panscan)) + +// 'Fit' one rect inside another. `rc` will be set to the same size and aspect +// ratio as `src`, but with the size limited to fit inside the original `rc`. +// Like `pl_rect2df_aspect_set`, `panscan` controls the pan&scan factor. +void pl_rect2df_aspect_fit(struct pl_rect2df *rc, const struct pl_rect2df *src, + float panscan); + +// Scale rect in each direction while keeping it centered. +void pl_rect2df_stretch(struct pl_rect2df *rc, float stretch_x, float stretch_y); + +// Offset rect by an arbitrary offset factor. If the corresponding dimension +// of a rect is flipped, so too is the applied offset. +void pl_rect2df_offset(struct pl_rect2df *rc, float offset_x, float offset_y); + +// Scale a rect uniformly in both dimensions. +#define pl_rect2df_zoom(rc, zoom) pl_rect2df_stretch((rc), (zoom), (zoom)) + +// Variants of the functions above that operate directly on rounded rects. +// Note: Applying multiple of these operations compounds rounding error in each +// step. Consider doing the calculations on pl_rect2df and rounding at the end. +float pl_rect2d_aspect(const struct pl_rect2d *rc); +void pl_rect2d_aspect_set(struct pl_rect2d *rc, float aspect, float panscan); +#define pl_rect2d_aspect_copy(rc, src, panscan) \ + pl_rect2d_aspect_set((rc), pl_rect2df_aspect(src), (panscan)) +void pl_rect2d_aspect_fit(struct pl_rect2d *rc, const struct pl_rect2df *src, + float panscan); +void pl_rect2d_stretch(struct pl_rect2d *rc, float stretch_x, float stretch_y); +void pl_rect2d_offset(struct pl_rect2d *rc, int offset_x, int offset_y); +#define pl_rect2d_zoom(rc, zoom) pl_rect2d_stretch((rc), (zoom), (zoom)) + #endif // LIBPLACEBO_COMMON_H_ diff --git a/src/include/libplacebo/renderer.h b/src/include/libplacebo/renderer.h index 0914136e99d0865d53c14b70649b70a997c4da10..11d7012494dbd6d6370d02fcd0425c82f044f7da 100644 --- a/src/include/libplacebo/renderer.h +++ b/src/include/libplacebo/renderer.h @@ -410,6 +410,11 @@ struct pl_render_target { void pl_render_target_from_swapchain(struct pl_render_target *out_target, const struct pl_swapchain_frame *frame); +// Helper function to determine if the `target` covers the entire FBO or not. +// If this returns true, users may want to `pl_tex_clear` the `target.fbo` +// before calling `pl_render_image`. +bool pl_render_target_partial(const struct pl_render_target *target); + // Render a single image to a target using the given parameters. This is // fully dynamic, i.e. the params can change at any time. libplacebo will // internally detect and flush whatever caches are invalidated as a result of diff --git a/src/renderer.c b/src/renderer.c index 58d9749ae036fb90a17acabcc6fb32a14c726a15..0fc212940543db0574c73274acf1a0de8e401edd 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -1652,3 +1652,20 @@ void pl_render_target_from_swapchain(struct pl_render_target *out_target, if (frame->flipped) PL_SWAP(out_target->dst_rect.y0, out_target->dst_rect.y1); } + +bool pl_render_target_partial(const struct pl_render_target *target) +{ + int x0 = PL_MIN(target->dst_rect.x0, target->dst_rect.x1), + y0 = PL_MIN(target->dst_rect.y0, target->dst_rect.y1), + x1 = PL_MAX(target->dst_rect.x0, target->dst_rect.x1), + y1 = PL_MAX(target->dst_rect.y0, target->dst_rect.y1), + fbo_w = target->fbo->params.w, + fbo_h = target->fbo->params.h; + + if (!x0 && !x1) + x1 = fbo_w; + if (!y0 && !y1) + y1 = fbo_h; + + return x0 > 0 || y0 > 0 || x1 < fbo_w || y1 < fbo_h; +} diff --git a/src/tests/context.c b/src/tests/context.c index 4921988f20e88fced6ac92d03cc47c9b864de19b..970e176d5d992c3319f84588371a512a2d84db38 100644 --- a/src/tests/context.c +++ b/src/tests/context.c @@ -50,8 +50,46 @@ int main() for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { printf("%f %f\n", tr.mat.m[i][j], tr2.mat.m[i][j]); - REQUIRE(fabs(tr.mat.m[i][j] - tr2.mat.m[i][j]) < 1e-4); + REQUIRE(feq(tr.mat.m[i][j], tr2.mat.m[i][j], 1e-4)); } - REQUIRE(fabs(tr.c[i] - tr2.c[i]) < 1e-4); + REQUIRE(feq(tr.c[i], tr2.c[i], 1e-4)); } + + // Test aspect ratio code + const struct pl_rect2df rc1080p = {0, 0, 1920, 1080}; + const struct pl_rect2df rc43 = {0, 0, 1024, 768}; + struct pl_rect2df rc; + + REQUIRE(feq(pl_rect2df_aspect(&rc1080p), 16.0/9.0, 1e-8)); + REQUIRE(feq(pl_rect2df_aspect(&rc43), 4.0/3.0, 1e-8)); + +#define pl_rect2df_midx(rc) (((rc).x0 + (rc).x1) / 2.0) +#define pl_rect2df_midy(rc) (((rc).y0 + (rc).y1) / 2.0) + + for (float aspect = 0.2; aspect < 3.0; aspect += 0.4) { + for (float scan = 0.0; scan <= 1.0; scan += 0.5) { + rc = rc1080p; + pl_rect2df_aspect_set(&rc, aspect, scan); + printf("aspect %.2f, panscan %.1f: {%f %f} -> {%f %f}\n", + aspect, scan, rc.x0, rc.y0, rc.x1, rc.y1); + REQUIRE(feq(pl_rect2df_aspect(&rc), aspect, 1e-6)); + REQUIRE(feq(pl_rect2df_midx(rc), pl_rect2df_midx(rc1080p), 1e-6)); + REQUIRE(feq(pl_rect2df_midy(rc), pl_rect2df_midy(rc1080p), 1e-6)); + } + } + + rc = rc1080p; + pl_rect2df_aspect_fit(&rc, &rc43, 0.0); + REQUIRE(feq(pl_rect2df_aspect(&rc), pl_rect2df_aspect(&rc43), 1e-6)); + REQUIRE(feq(pl_rect2df_midx(rc), pl_rect2df_midx(rc1080p), 1e-6)); + REQUIRE(feq(pl_rect2df_midy(rc), pl_rect2df_midy(rc1080p), 1e-6)); + REQUIRE(feq(pl_rect_w(rc), pl_rect_w(rc43), 1e-6)); + REQUIRE(feq(pl_rect_h(rc), pl_rect_h(rc43), 1e-6)); + + rc = rc43; + pl_rect2df_aspect_fit(&rc, &rc1080p, 0.0); + REQUIRE(feq(pl_rect2df_aspect(&rc), pl_rect2df_aspect(&rc1080p), 1e-6)); + REQUIRE(feq(pl_rect2df_midx(rc), pl_rect2df_midx(rc43), 1e-6)); + REQUIRE(feq(pl_rect2df_midy(rc), pl_rect2df_midy(rc43), 1e-6)); + REQUIRE(feq(pl_rect_w(rc), pl_rect_w(rc43), 1e-6)); }