2023-04-01 02:20:27 +08:00
|
|
|
#include "display.h"
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "util/log.h"
|
|
|
|
|
|
|
|
bool
|
|
|
|
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
|
|
|
|
display->renderer =
|
|
|
|
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
|
|
|
if (!display->renderer) {
|
|
|
|
LOGE("Could not create renderer: %s", SDL_GetError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RendererInfo renderer_info;
|
|
|
|
int r = SDL_GetRendererInfo(display->renderer, &renderer_info);
|
|
|
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
|
|
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
|
|
|
|
|
|
|
display->mipmaps = false;
|
|
|
|
|
|
|
|
// starts with "opengl"
|
|
|
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
|
|
|
if (use_opengl) {
|
2023-04-05 22:04:03 +08:00
|
|
|
|
|
|
|
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
|
|
|
// Persuade macOS to give us something better than OpenGL 2.1.
|
|
|
|
// If we create a Core Profile context, we get the best OpenGL version.
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
|
|
|
SDL_GL_CONTEXT_PROFILE_CORE);
|
|
|
|
|
|
|
|
LOGD("Creating OpenGL Core Profile context");
|
|
|
|
display->gl_context = SDL_GL_CreateContext(window);
|
|
|
|
if (!display->gl_context) {
|
|
|
|
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
|
|
|
SDL_DestroyRenderer(display->renderer);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2023-04-01 02:20:27 +08:00
|
|
|
struct sc_opengl *gl = &display->gl;
|
|
|
|
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");
|
|
|
|
display->mipmaps = true;
|
|
|
|
} else {
|
|
|
|
LOGW("Trilinear filtering disabled "
|
|
|
|
"(OpenGL 3.0+ or ES 2.0+ required");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOGI("Trilinear filtering disabled");
|
|
|
|
}
|
|
|
|
} else if (mipmaps) {
|
|
|
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer");
|
|
|
|
}
|
|
|
|
|
2023-05-09 00:16:38 +08:00
|
|
|
display->pending.flags = 0;
|
|
|
|
display->pending.frame = NULL;
|
|
|
|
|
2023-04-01 02:20:27 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
sc_display_destroy(struct sc_display *display) {
|
2023-05-09 00:16:38 +08:00
|
|
|
if (display->pending.frame) {
|
|
|
|
av_frame_free(&display->pending.frame);
|
|
|
|
}
|
2023-04-05 22:04:03 +08:00
|
|
|
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
|
|
|
SDL_GL_DeleteContext(display->gl_context);
|
|
|
|
#endif
|
2023-04-01 02:20:27 +08:00
|
|
|
if (display->texture) {
|
|
|
|
SDL_DestroyTexture(display->texture);
|
|
|
|
}
|
|
|
|
SDL_DestroyRenderer(display->renderer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static SDL_Texture *
|
|
|
|
sc_display_create_texture(struct sc_display *display,
|
|
|
|
struct sc_size size) {
|
|
|
|
SDL_Renderer *renderer = display->renderer;
|
|
|
|
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
size.width, size.height);
|
|
|
|
if (!texture) {
|
2023-05-09 00:16:38 +08:00
|
|
|
LOGD("Could not create texture: %s", SDL_GetError());
|
2023-04-01 02:20:27 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (display->mipmaps) {
|
|
|
|
struct sc_opengl *gl = &display->gl;
|
|
|
|
|
|
|
|
SDL_GL_BindTexture(texture, NULL, NULL);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
SDL_GL_UnbindTexture(texture);
|
|
|
|
}
|
|
|
|
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
2023-05-09 00:16:38 +08:00
|
|
|
static inline void
|
|
|
|
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
|
|
|
|
assert(!display->texture);
|
|
|
|
display->pending.size = size;
|
|
|
|
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
|
|
|
if (!display->pending.frame) {
|
|
|
|
display->pending.frame = av_frame_alloc();
|
|
|
|
if (!display->pending.frame) {
|
|
|
|
LOG_OOM();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int r = av_frame_ref(display->pending.frame, frame);
|
|
|
|
if (r) {
|
|
|
|
LOGE("Could not ref frame: %d", r);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
sc_display_apply_pending(struct sc_display *display) {
|
|
|
|
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
|
|
|
|
assert(!display->texture);
|
|
|
|
display->texture =
|
|
|
|
sc_display_create_texture(display, display->pending.size);
|
|
|
|
if (!display->texture) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
|
|
|
assert(display->pending.frame);
|
|
|
|
bool ok = sc_display_update_texture(display, display->pending.frame);
|
|
|
|
if (!ok) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
av_frame_unref(display->pending.frame);
|
|
|
|
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
sc_display_set_texture_size_internal(struct sc_display *display,
|
|
|
|
struct sc_size size) {
|
|
|
|
assert(size.width && size.height);
|
|
|
|
|
2023-04-01 02:20:27 +08:00
|
|
|
if (display->texture) {
|
|
|
|
SDL_DestroyTexture(display->texture);
|
|
|
|
}
|
|
|
|
|
|
|
|
display->texture = sc_display_create_texture(display, size);
|
|
|
|
if (!display->texture) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-05-09 00:16:38 +08:00
|
|
|
enum sc_display_result
|
|
|
|
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
|
|
|
bool ok = sc_display_set_texture_size_internal(display, size);
|
|
|
|
if (!ok) {
|
|
|
|
sc_display_set_pending_size(display, size);
|
|
|
|
return SC_DISPLAY_RESULT_PENDING;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return SC_DISPLAY_RESULT_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
sc_display_update_texture_internal(struct sc_display *display,
|
|
|
|
const AVFrame *frame) {
|
2023-04-01 02:20:27 +08:00
|
|
|
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
|
|
|
|
frame->data[0], frame->linesize[0],
|
|
|
|
frame->data[1], frame->linesize[1],
|
|
|
|
frame->data[2], frame->linesize[2]);
|
|
|
|
if (ret) {
|
2023-05-09 00:16:38 +08:00
|
|
|
LOGD("Could not update texture: %s", SDL_GetError());
|
2023-04-01 02:20:27 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (display->mipmaps) {
|
|
|
|
SDL_GL_BindTexture(display->texture, NULL, NULL);
|
|
|
|
display->gl.GenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
SDL_GL_UnbindTexture(display->texture);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-05-09 00:16:38 +08:00
|
|
|
enum sc_display_result
|
|
|
|
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
|
|
|
bool ok = sc_display_update_texture_internal(display, frame);
|
|
|
|
if (!ok) {
|
|
|
|
ok = sc_display_set_pending_frame(display, frame);
|
|
|
|
if (!ok) {
|
|
|
|
LOGE("Could not set pending frame");
|
|
|
|
return SC_DISPLAY_RESULT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SC_DISPLAY_RESULT_PENDING;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SC_DISPLAY_RESULT_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum sc_display_result
|
2023-04-01 02:20:27 +08:00
|
|
|
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
|
|
|
unsigned rotation) {
|
|
|
|
SDL_RenderClear(display->renderer);
|
|
|
|
|
2023-05-09 00:16:38 +08:00
|
|
|
if (display->pending.flags) {
|
|
|
|
bool ok = sc_display_apply_pending(display);
|
|
|
|
if (!ok) {
|
|
|
|
return SC_DISPLAY_RESULT_PENDING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-01 02:20:27 +08:00
|
|
|
SDL_Renderer *renderer = display->renderer;
|
|
|
|
SDL_Texture *texture = display->texture;
|
|
|
|
|
|
|
|
if (rotation == 0) {
|
|
|
|
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
|
|
|
|
if (ret) {
|
|
|
|
LOGE("Could not render texture: %s", SDL_GetError());
|
2023-05-09 00:16:38 +08:00
|
|
|
return SC_DISPLAY_RESULT_ERROR;
|
2023-04-01 02:20:27 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
|
|
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
|
|
|
int cw_rotation = (4 - rotation) % 4;
|
|
|
|
double angle = 90 * cw_rotation;
|
|
|
|
|
|
|
|
const SDL_Rect *dstrect = NULL;
|
|
|
|
SDL_Rect rect;
|
|
|
|
if (rotation & 1) {
|
|
|
|
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
|
|
|
|
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
|
|
|
|
rect.w = geometry->h;
|
|
|
|
rect.h = geometry->w;
|
|
|
|
dstrect = ▭
|
|
|
|
} else {
|
|
|
|
assert(rotation == 2);
|
|
|
|
dstrect = geometry;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
|
|
|
|
NULL, 0);
|
|
|
|
if (ret) {
|
|
|
|
LOGE("Could not render texture: %s", SDL_GetError());
|
2023-05-09 00:16:38 +08:00
|
|
|
return SC_DISPLAY_RESULT_ERROR;
|
2023-04-01 02:20:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RenderPresent(display->renderer);
|
2023-05-09 00:16:38 +08:00
|
|
|
return SC_DISPLAY_RESULT_OK;
|
2023-04-01 02:20:27 +08:00
|
|
|
}
|