2026-01-31 21:38:32 +01:00
|
|
|
#include "texture.h"
|
2023-03-31 20:20:27 +02:00
|
|
|
|
|
|
|
|
#include <assert.h>
|
2024-12-20 20:58:41 +01:00
|
|
|
#include <inttypes.h>
|
|
|
|
|
#include <string.h>
|
2024-03-30 15:18:44 +01:00
|
|
|
#include <libavutil/pixfmt.h>
|
2023-03-31 20:20:27 +02:00
|
|
|
|
|
|
|
|
#include "util/log.h"
|
|
|
|
|
|
|
|
|
|
bool
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
|
2026-01-24 22:40:54 +01:00
|
|
|
const char *renderer_name = SDL_GetRendererName(renderer);
|
2023-03-31 20:20:27 +02:00
|
|
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->mipmaps = false;
|
2023-03-31 20:20:27 +02:00
|
|
|
|
|
|
|
|
// starts with "opengl"
|
|
|
|
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
|
|
|
|
if (use_opengl) {
|
2026-01-31 21:38:32 +01:00
|
|
|
struct sc_opengl *gl = &tex->gl;
|
2023-03-31 20:20:27 +02:00
|
|
|
sc_opengl_init(gl);
|
|
|
|
|
|
|
|
|
|
LOGI("OpenGL version: %s", gl->version);
|
|
|
|
|
|
|
|
|
|
if (mipmaps) {
|
|
|
|
|
bool supports_mipmaps =
|
|
|
|
|
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
|
|
|
|
2, 0 /* OpenGL ES 2.0+ */);
|
|
|
|
|
if (supports_mipmaps) {
|
|
|
|
|
LOGI("Trilinear filtering enabled");
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->mipmaps = true;
|
2023-03-31 20:20:27 +02:00
|
|
|
} else {
|
|
|
|
|
LOGW("Trilinear filtering disabled "
|
2023-08-07 20:22:17 +02:00
|
|
|
"(OpenGL 3.0+ or ES 2.0+ required)");
|
2023-03-31 20:20:27 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOGI("Trilinear filtering disabled");
|
|
|
|
|
}
|
|
|
|
|
} else if (mipmaps) {
|
2024-03-01 00:11:48 +01:00
|
|
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
2023-03-31 20:20:27 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->renderer = renderer;
|
|
|
|
|
tex->texture = NULL;
|
2023-03-31 20:20:27 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_destroy(struct sc_texture *tex) {
|
|
|
|
|
if (tex->texture) {
|
|
|
|
|
SDL_DestroyTexture(tex->texture);
|
2023-03-31 20:20:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 09:42:03 +02:00
|
|
|
static enum SDL_Colorspace
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_to_sdl_color_space(enum AVColorSpace color_space,
|
2025-07-11 09:42:03 +02:00
|
|
|
enum AVColorRange color_range) {
|
|
|
|
|
bool full_range = color_range == AVCOL_RANGE_JPEG;
|
|
|
|
|
|
2025-07-10 23:07:45 +02:00
|
|
|
switch (color_space) {
|
|
|
|
|
case AVCOL_SPC_BT709:
|
2025-09-15 11:13:06 -04:00
|
|
|
case AVCOL_SPC_RGB:
|
2025-07-11 09:42:03 +02:00
|
|
|
return full_range ? SDL_COLORSPACE_BT709_FULL
|
|
|
|
|
: SDL_COLORSPACE_BT709_LIMITED;
|
2025-07-10 23:07:45 +02:00
|
|
|
case AVCOL_SPC_BT470BG:
|
|
|
|
|
case AVCOL_SPC_SMPTE170M:
|
2025-07-11 09:42:03 +02:00
|
|
|
return full_range ? SDL_COLORSPACE_BT601_FULL
|
|
|
|
|
: SDL_COLORSPACE_BT601_LIMITED;
|
|
|
|
|
case AVCOL_SPC_BT2020_NCL:
|
|
|
|
|
case AVCOL_SPC_BT2020_CL:
|
|
|
|
|
return full_range ? SDL_COLORSPACE_BT2020_FULL
|
|
|
|
|
: SDL_COLORSPACE_BT2020_LIMITED;
|
2025-07-10 23:07:45 +02:00
|
|
|
default:
|
2025-07-11 09:42:03 +02:00
|
|
|
return SDL_COLORSPACE_JPEG;
|
2025-07-10 23:07:45 +02:00
|
|
|
}
|
2025-07-03 19:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-31 20:20:27 +02:00
|
|
|
static SDL_Texture *
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_create_frame_texture(struct sc_texture *tex,
|
2026-01-28 19:39:53 +01:00
|
|
|
struct sc_size size,
|
|
|
|
|
enum AVColorSpace color_space,
|
|
|
|
|
enum AVColorRange color_range) {
|
2025-07-11 09:42:03 +02:00
|
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
|
|
|
if (!props) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum SDL_Colorspace sdl_color_space =
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_to_sdl_color_space(color_space, color_range);
|
2025-07-11 09:42:03 +02:00
|
|
|
|
|
|
|
|
bool ok =
|
|
|
|
|
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
|
|
|
|
|
SDL_PIXELFORMAT_YV12);
|
|
|
|
|
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
|
|
|
|
|
SDL_TEXTUREACCESS_STREAMING);
|
|
|
|
|
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
|
|
|
|
|
size.width);
|
|
|
|
|
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
|
|
|
|
|
size.height);
|
|
|
|
|
ok &= SDL_SetNumberProperty(props,
|
|
|
|
|
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
|
|
|
|
|
sdl_color_space);
|
|
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
LOGE("Could not set texture properties");
|
|
|
|
|
SDL_DestroyProperties(props);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
SDL_Renderer *renderer = tex->renderer;
|
2025-07-11 09:42:03 +02:00
|
|
|
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
|
|
|
|
SDL_DestroyProperties(props);
|
2023-03-31 20:20:27 +02:00
|
|
|
if (!texture) {
|
2023-05-08 18:16:38 +02:00
|
|
|
LOGD("Could not create texture: %s", SDL_GetError());
|
2023-03-31 20:20:27 +02:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
if (tex->mipmaps) {
|
|
|
|
|
struct sc_opengl *gl = &tex->gl;
|
2023-03-31 20:20:27 +02:00
|
|
|
|
2025-07-11 09:42:03 +02:00
|
|
|
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
|
|
|
|
if (!props) {
|
|
|
|
|
LOGE("Could not get texture properties: %s", SDL_GetError());
|
|
|
|
|
SDL_DestroyTexture(texture);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
const char *renderer_name = SDL_GetRendererName(tex->renderer);
|
2025-07-11 09:42:03 +02:00
|
|
|
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
|
|
|
|
|
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
|
|
|
|
|
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
|
|
|
|
|
|
|
|
|
|
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
|
|
|
|
|
SDL_DestroyProperties(props);
|
|
|
|
|
if (!texture_id) {
|
|
|
|
|
LOGE("Could not get texture id: %s", SDL_GetError());
|
|
|
|
|
SDL_DestroyTexture(texture);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->texture_id = texture_id;
|
|
|
|
|
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
2023-03-31 20:20:27 +02:00
|
|
|
|
|
|
|
|
// Enable trilinear filtering for downscaling
|
|
|
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
|
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
|
|
|
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
|
|
|
|
|
2025-07-11 09:42:03 +02:00
|
|
|
gl->BindTexture(GL_TEXTURE_2D, 0);
|
2023-03-31 20:20:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return texture;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 21:51:13 +01:00
|
|
|
bool
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame) {
|
2026-01-28 19:39:53 +01:00
|
|
|
|
|
|
|
|
struct sc_size size = {frame->width, frame->height};
|
2023-05-08 18:16:38 +02:00
|
|
|
assert(size.width && size.height);
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
if (!tex->texture
|
|
|
|
|
|| tex->texture_type != SC_TEXTURE_TYPE_FRAME
|
|
|
|
|
|| tex->texture_size.width != size.width
|
|
|
|
|
|| tex->texture_size.height != size.height) {
|
2026-01-28 19:39:53 +01:00
|
|
|
// Incompatible texture, recreate it
|
|
|
|
|
enum AVColorSpace color_space = frame->colorspace;
|
|
|
|
|
enum AVColorRange color_range = frame->color_range;
|
2023-03-31 20:20:27 +02:00
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
if (tex->texture) {
|
|
|
|
|
SDL_DestroyTexture(tex->texture);
|
2026-01-28 19:39:53 +01:00
|
|
|
}
|
2023-03-31 20:20:27 +02:00
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->texture = sc_texture_create_frame_texture(tex, size, color_space,
|
|
|
|
|
color_range);
|
|
|
|
|
if (!tex->texture) {
|
2026-01-28 19:39:53 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->texture_size = size;
|
|
|
|
|
tex->texture_type = SC_TEXTURE_TYPE_FRAME;
|
2026-01-28 19:39:53 +01:00
|
|
|
|
|
|
|
|
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
|
|
|
|
}
|
2023-03-31 20:20:27 +02:00
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
assert(tex->texture);
|
|
|
|
|
assert(tex->texture_type == SC_TEXTURE_TYPE_FRAME);
|
2026-01-28 19:39:53 +01:00
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
bool ok = SDL_UpdateYUVTexture(tex->texture, NULL,
|
2023-03-31 20:20:27 +02:00
|
|
|
frame->data[0], frame->linesize[0],
|
|
|
|
|
frame->data[1], frame->linesize[1],
|
|
|
|
|
frame->data[2], frame->linesize[2]);
|
2025-07-11 09:42:03 +02:00
|
|
|
if (!ok) {
|
2023-05-08 18:16:38 +02:00
|
|
|
LOGD("Could not update texture: %s", SDL_GetError());
|
2023-03-31 20:20:27 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
if (tex->mipmaps) {
|
|
|
|
|
assert(tex->texture_id);
|
|
|
|
|
struct sc_opengl *gl = &tex->gl;
|
2025-07-11 09:42:03 +02:00
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
2025-07-11 09:42:03 +02:00
|
|
|
gl->GenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
|
gl->BindTexture(GL_TEXTURE_2D, 0);
|
2023-03-31 20:20:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-01-31 21:37:40 +01:00
|
|
|
|
|
|
|
|
bool
|
2026-01-31 21:38:32 +01:00
|
|
|
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
|
|
|
|
|
if (tex->texture) {
|
|
|
|
|
SDL_DestroyTexture(tex->texture);
|
2026-01-31 21:37:40 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->texture = SDL_CreateTextureFromSurface(tex->renderer, surface);
|
|
|
|
|
if (!tex->texture) {
|
2026-01-31 21:37:40 +01:00
|
|
|
LOGE("Could not create texture: %s", SDL_GetError());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 21:38:32 +01:00
|
|
|
tex->texture_size.width = surface->w;
|
|
|
|
|
tex->texture_size.height = surface->h;
|
|
|
|
|
tex->texture_type = SC_TEXTURE_TYPE_ICON;
|
2026-01-28 19:39:53 +01:00
|
|
|
|
2026-01-31 21:37:40 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-02-01 15:56:18 +01:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
sc_texture_reset(struct sc_texture *tex) {
|
|
|
|
|
if (tex->texture) {
|
|
|
|
|
SDL_DestroyTexture(tex->texture);
|
|
|
|
|
tex->texture = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|