taisei/src/video.c

1164 lines
33 KiB
C
Raw Normal View History

2012-07-28 22:53:53 +02:00
/*
* This software is licensed under the terms of the MIT License.
2012-07-28 22:53:53 +02:00
* See COPYING for further information.
* ---
2024-05-16 23:30:41 +02:00
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
2012-07-28 22:53:53 +02:00
*/
#include "video.h"
#include "dynarray.h"
#include "events.h"
#include "global.h"
#include "renderer/api.h"
#include "rwops/rwops_autobuf.h"
#include "stagedraw.h"
#include "taskmanager.h"
#include "util/env.h"
#include "util/fbmgr.h"
#include "util/graphics.h"
#include "util/io.h"
#include "version.h"
#include "video_postprocess.h"
Refactored the gl loader into a polyglot macro monstrosity We no longer link to libGL by default. All GL functions are loaded dynamically through SDL apis. Use -DLINK_TO_LIBGL to enable linking, in which case Taisei won't try to dynamically load any of the gl functions. Previously we used a strange inconsistent setup where some functions were loaded dynamically and others pulled from the linked library. We also no longer link to libGLU even if LINK_TO_LIBGL is set. The only function we used from that library was gluPerspective. taiseigl now provides a simple substitute via glFrustum. The SDL2 gl headers are now used instead of the system ones, this should be more portable. The taiseigl.h header contains generated code partially based on content from those headers. It also doubles as a python3 script that actually generates that code and inserts it into itself. It scans the Taisei source tree for gl* calls and generates code only for the functions we use, and a few manually specified ones that are called indirectly. Assumptions such as "linux = glx" are no longer made. SDL takes care of platform specifics, as it should. The GL_EXT_draw_instanced/GL_ARB_draw_instanced extension detection has been improved. Taisei should be able to figure out which one to use without a compile-time check, and support for the ARB version has been actually implemented for the laser snippet loader. I've tested it on Windows 8 with Intel drivers that don't support the EXT version but do support the ARB one, instanced drawing works and the lasers don't lag! OSX should benefit from this change as well, although I've not yet tested the OSX build, beyond simply compiling it.
2017-02-24 01:54:28 +01:00
typedef DYNAMIC_ARRAY(VideoMode) VideoModeArray;
typedef enum FramedumpSource {
FRAMEDUMP_SRC_SCREEN,
FRAMEDUMP_SRC_VIEWPORT,
} FramedumpSource;
static struct {
VideoModeArray fs_modes;
VideoModeArray win_modes;
SDL_Window *window;
VideoPostProcess *postprocess;
VideoMode intended;
VideoMode current;
VideoBackend backend;
double scaling_factor;
uint num_resize_events;
struct {
char *name_prefix;
size_t name_prefix_len;
size_t frame_count;
int compression;
FramedumpSource source;
} framedump;
} video;
2012-07-28 22:53:53 +02:00
#define FRAMEDUMP_FILENAME_SUFFIX ".png"
#define FRAMEDUMP_FILENAME_NUM_DIGITS 8
#define FRAMEDUMP_FILENAME_EXTRA_BUFSIZE \
(FRAMEDUMP_FILENAME_NUM_DIGITS + sizeof(FRAMEDUMP_FILENAME_SUFFIX))
#define FRAMEDUMP_FILENAME_FORMAT "%08u" FRAMEDUMP_FILENAME_SUFFIX
VideoCapabilityState (*video_query_capability)(VideoCapability cap);
typedef struct ScreenshotTaskData {
char *dest_path; // NULL if in framedump mode
OpenGL ES 3.0 rendering backend (#148) * First steps towards shader transpilation Needs to be manually enabled via -Dshader_transpiler=true. Requires shaderc. https://github.com/google/shaderc Not yet functional due to missing SPIRV-Cross integration. SPIRV-Cross currently does not have an official C API, and crossc is too minimal to be useful. The current plan is to extend crossc and vendor it, while also sending PRs upstream. * Integrate crossc; shader transpilation for GLES now works * fix leak * gles30 backend now playable on Mesa with 3.2 context Some rendering issues are present. Identified so far: - Marisa's lasers are invisible - Death effect looks wrong Also, a small pixmap manipulation library has been written, and the texture uploading API redesigned around it. * fix marisa lasers in GLES (uniform name clashed with builtin) * fix player death effect in GLES (another name clash) * Dump ANGLE's translated shader code in debug log * fix screenshots * Drop support for triangle fans, switch to strips Fans offer no advantage over strips, and they've been removed in D3D10+, so ANGLE has to emulate them. * crude workaround for an ANGLE bug * Re-enable GL debug labels, fix an issue with them that affected ANGLE (but was always technically a bug) * fix race condition in shaderc initialization * New SDL_RWops interface for vertex buffers * Optimize VBO streaming via buffering updates Measurable performance improvement even with the main gl33 renderer, drastic improvement with ANGLE. * Fix the depth texture binding problem under ANGLE Apparently it hates GL_DEPTH_COMPONENT16 for some reason. Sized internal formats are not supported in GLES 2.0 anyway, so not using them is probably a good idea. * fix GLES2.0 segfault (the backend still doesn't work, though) * dump GL extensions at info log level, not debug * get around a Mesa bug; more correct texture format table for GLES2 * Correct GLES3 texture format table according to the spec Not a Mesa bug after all * require crossc>=1.5.0, fallback to subproject * Request at least 8bit per color channel in GL backends * Forbid lto for static windows builds with shader_transpiler=true * fix edge case segfault * Add basic ANGLE bundling support to the build system Windows only, and no NSIS support yet * Fix various windows-related build system and installer brokenness * Disable gles backends by default * update documentation
2018-10-02 00:36:10 +02:00
Pixmap image;
uint32_t frame_num; // for framedump mode only
} ScreenshotTaskData;
2012-07-28 22:53:53 +02:00
#define VIDEO_MIN_SIZE_FACTOR 0.8
#define VIDEO_MIN_WIDTH (int)(SCREEN_W * VIDEO_MIN_SIZE_FACTOR)
#define VIDEO_MIN_HEIGHT (int)(SCREEN_H * VIDEO_MIN_SIZE_FACTOR)
/*
* BEGIN Conversion between screen-space and pixel-space coordinates (for high-DPI mode)
* TODO: figure out how to round these correctly
*/
attr_unused static inline int coords_val_screen_to_pixels(int screen_coord) {
return round(screen_coord * video.scaling_factor);
}
attr_unused static inline int coords_val_pixels_to_screen(int pixel_coord) {
return round(pixel_coord / video.scaling_factor);
}
attr_unused static inline IntOffset coords_ofs_screen_to_pixels(IntOffset screen_ofs) {
IntOffset pixel_ofs;
pixel_ofs.x = coords_val_screen_to_pixels(screen_ofs.x);
pixel_ofs.y = coords_val_screen_to_pixels(screen_ofs.y);
return pixel_ofs;
}
attr_unused static inline IntOffset coords_ofs_pixels_to_screen(IntOffset pixel_ofs) {
IntOffset screen_ofs;
screen_ofs.x = coords_val_pixels_to_screen(pixel_ofs.x);
screen_ofs.y = coords_val_pixels_to_screen(pixel_ofs.y);
return screen_ofs;
}
attr_unused static inline IntExtent coords_ext_screen_to_pixels(IntExtent screen_ext) {
IntExtent pixel_ext;
pixel_ext.w = coords_val_screen_to_pixels(screen_ext.w);
pixel_ext.h = coords_val_screen_to_pixels(screen_ext.h);
return pixel_ext;
}
attr_unused static inline IntExtent coords_ext_pixels_to_screen(IntExtent pixel_ext) {
IntExtent screen_ext;
screen_ext.w = coords_val_pixels_to_screen(pixel_ext.w);
screen_ext.h = coords_val_pixels_to_screen(pixel_ext.h);
return screen_ext;
}
attr_unused static inline IntRect coords_rect_screen_to_pixels(IntRect screen_rect) {
IntRect pixel_rect;
pixel_rect.extent = coords_ext_screen_to_pixels(screen_rect.extent);
pixel_rect.offset = coords_ofs_screen_to_pixels(screen_rect.offset);
return pixel_rect;
}
attr_unused static inline IntRect coords_rect_pixels_to_screen(IntRect pixel_rect) {
IntRect screen_rect;
screen_rect.extent = coords_ext_pixels_to_screen(pixel_rect.extent);
screen_rect.offset = coords_ofs_pixels_to_screen(pixel_rect.offset);
return screen_rect;
}
/*
* END Conversion between screen-space and pixel-space coordinates (for high-DPI mode)
*/
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
static VideoCapabilityState video_query_capability_generic(VideoCapability cap) {
switch(cap) {
case VIDEO_CAP_FULLSCREEN:
return VIDEO_AVAILABLE;
case VIDEO_CAP_EXTERNAL_RESIZE:
return video_is_fullscreen() ? VIDEO_CURRENTLY_UNAVAILABLE : VIDEO_AVAILABLE;
case VIDEO_CAP_CHANGE_RESOLUTION:
return video_is_fullscreen() ? VIDEO_CURRENTLY_UNAVAILABLE : VIDEO_AVAILABLE;
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
case VIDEO_CAP_VSYNC_ADAPTIVE:
return VIDEO_AVAILABLE;
}
UNREACHABLE;
}
static VideoCapabilityState video_query_capability_alwaysfullscreen(VideoCapability cap) {
switch(cap) {
case VIDEO_CAP_FULLSCREEN:
return VIDEO_ALWAYS_ENABLED;
case VIDEO_CAP_EXTERNAL_RESIZE:
return VIDEO_NEVER_AVAILABLE;
// XXX: Might not be actually working, but let's be optimistic.
case VIDEO_CAP_CHANGE_RESOLUTION:
return VIDEO_AVAILABLE;
case VIDEO_CAP_VSYNC_ADAPTIVE:
return VIDEO_AVAILABLE;
}
UNREACHABLE;
}
2023-10-10 18:31:23 +02:00
static VideoCapabilityState video_query_capability_switch(VideoCapability cap) {
switch(cap) {
// We want the window to be resizable and resized internally by SDL
// when the Switch gets docked/undocked
case VIDEO_CAP_FULLSCREEN:
return VIDEO_NEVER_AVAILABLE;
case VIDEO_CAP_EXTERNAL_RESIZE:
return VIDEO_AVAILABLE;
case VIDEO_CAP_CHANGE_RESOLUTION:
return VIDEO_NEVER_AVAILABLE;
case VIDEO_CAP_VSYNC_ADAPTIVE:
return VIDEO_NEVER_AVAILABLE;
}
UNREACHABLE;
}
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
static VideoCapabilityState video_query_capability_webcanvas(VideoCapability cap) {
switch(cap) {
case VIDEO_CAP_EXTERNAL_RESIZE:
return VIDEO_ALWAYS_ENABLED;
case VIDEO_CAP_FULLSCREEN:
return VIDEO_NEVER_AVAILABLE;
case VIDEO_CAP_CHANGE_RESOLUTION:
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
return VIDEO_NEVER_AVAILABLE;
case VIDEO_CAP_VSYNC_ADAPTIVE:
return VIDEO_NEVER_AVAILABLE;
default:
return video_query_capability_generic(cap);
}
}
2023-06-20 00:50:17 +02:00
static VideoCapabilityState (*video_query_capability_kiosk_fallback)(VideoCapability cap);
static VideoCapabilityState video_query_capability_kiosk(VideoCapability cap) {
switch(cap) {
case VIDEO_CAP_FULLSCREEN:
return VIDEO_ALWAYS_ENABLED;
case VIDEO_CAP_CHANGE_RESOLUTION:
return VIDEO_NEVER_AVAILABLE;
case VIDEO_CAP_EXTERNAL_RESIZE:
return VIDEO_NEVER_AVAILABLE;
default:
return video_query_capability_kiosk_fallback(cap);
}
}
static void video_add_mode(VideoModeArray *mode_array, IntExtent mode_screen, IntExtent min_screen, IntExtent max_screen, const char *mode_type) {
if(
(mode_screen.w > max_screen.w && max_screen.w > 0) ||
(mode_screen.h > max_screen.h && max_screen.h > 0)
) {
log_debug("Mode %ix%i rejected: > %ix%i", mode_screen.w, mode_screen.h, max_screen.w, max_screen.h);
return;
}
if(
mode_screen.w < min_screen.w ||
mode_screen.h < min_screen.h
) {
log_debug("Mode %ix%i rejected: < %ix%i", mode_screen.w, mode_screen.h, min_screen.w, min_screen.h);
return;
}
for(uint i = 0; i < mode_array->num_elements; ++i) {
VideoMode *m = mode_array->data + i;
if(m->width == mode_screen.w && m->height == mode_screen.h) {
log_debug("Mode %ix%i rejected: already registered", mode_screen.w, mode_screen.h);
return;
2012-07-28 22:53:53 +02:00
}
}
dynarray_append(mode_array, { .as_int_extent = mode_screen });
log_debug("Add %s mode: %ix%i", mode_type, mode_screen.w, mode_screen.h);
}
static void video_add_mode_dpi_aware(VideoModeArray *mode_array, IntExtent mode_pix, IntExtent min_screen, IntExtent max_screen, const char *mode_type) {
IntExtent mode_screen = coords_ext_pixels_to_screen(mode_pix);
// Yes, we add both. The pixel-space size is interpreted as screen-space.
// Anything too large to fit on the screen is rejected.
video_add_mode(mode_array, mode_screen, min_screen, max_screen, mode_type);
video_add_mode(mode_array, mode_pix, min_screen, max_screen, mode_type);
}
static void video_add_mode_fullscreen(IntExtent mode_pix, IntExtent min_screen, IntExtent max_screen) {
video_add_mode_dpi_aware(&video.fs_modes, mode_pix, min_screen, max_screen, "fullscreen");
}
static void video_add_mode_windowed(IntExtent mode_pix, IntExtent min_screen, IntExtent max_screen) {
video_add_mode_dpi_aware(&video.win_modes, mode_pix, min_screen, max_screen, "windowed");
2012-07-28 22:53:53 +02:00
}
static int video_compare_modes(const void *a, const void *b) {
const VideoMode *va = a;
const VideoMode *vb = b;
2012-07-28 22:53:53 +02:00
return va->width * va->height - vb->width * vb->height;
}
static IntExtent video_get_screen_framebuffer_size(void) {
return r_framebuffer_get_size(NULL);
}
static FloatExtent video_get_viewport_size_for_framebuffer(IntExtent framebuffer_size) {
float w = framebuffer_size.w;
float h = framebuffer_size.h;
float r = w / h;
if(r > VIDEO_ASPECT_RATIO) {
w = h * VIDEO_ASPECT_RATIO;
} else if(r < VIDEO_ASPECT_RATIO) {
h = w / VIDEO_ASPECT_RATIO;
}
return (FloatExtent) { w, h };
}
static IntExtent round_viewport_size(FloatExtent vp) {
return (IntExtent) { round(vp.w), round(vp.h) };
}
static void video_update_mode_lists(void) {
video.fs_modes.num_elements = 0;
video.win_modes.num_elements = 0;
dynarray_ensure_capacity(&video.fs_modes, 16);
dynarray_ensure_capacity(&video.win_modes, 16);
bool fullscreen_available = false;
bool has_windowed_modes = (video_query_capability(VIDEO_CAP_FULLSCREEN) != VIDEO_ALWAYS_ENABLED);
FloatExtent largest_fullscreen_viewport = { 0, 0 };
IntExtent screenspace_min_size = { VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT };
screenspace_min_size = coords_ext_pixels_to_screen(screenspace_min_size);
// Register all resolutions that are available in fullscreen and their corresponding windowed modes.
for(int s = 0; s < video_num_displays(); ++s) {
log_info("Found display #%i: %s", s, video_display_name(s));
SDL_DisplayMode desktop_mode;
IntExtent screenspace_max_size = { 0 };
if(SDL_GetDesktopDisplayMode(s, &desktop_mode)) {
log_sdl_error(LOG_WARN, "SDL_GetDesktopDisplayMode");
} else {
log_debug("Desktop mode: %ix%i@%iHz", desktop_mode.w, desktop_mode.h, desktop_mode.refresh_rate);
screenspace_max_size.w = desktop_mode.w;
screenspace_max_size.h = desktop_mode.h;
screenspace_max_size = coords_ext_pixels_to_screen(screenspace_max_size);
log_debug("Scaled screen-space bounds: %ix%i", screenspace_max_size.w, screenspace_max_size.h);
}
for(int i = 0; i < SDL_GetNumDisplayModes(s); ++i) {
SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 };
if(SDL_GetDisplayMode(s, i, &mode) != 0) {
log_sdl_error(LOG_WARN, "SDL_GetDisplayMode");
} else {
log_debug("Display mode #%i: %ix%i@%iHz", i, mode.w, mode.h, mode.refresh_rate);
video_add_mode_fullscreen((IntExtent) { mode.w, mode.h }, screenspace_min_size, screenspace_max_size);
fullscreen_available = true;
if(has_windowed_modes) {
FloatExtent vp = video_get_viewport_size_for_framebuffer((IntExtent) { mode.w, mode.h });
video_add_mode_windowed(round_viewport_size(vp), screenspace_min_size, screenspace_max_size);
// the ratio is always constant, so we need to check only 1 dimension
if(vp.w > largest_fullscreen_viewport.w) {
largest_fullscreen_viewport = vp;
}
}
}
}
}
if(has_windowed_modes) {
// Insert some more windowed modes derived from our "ideal" resolution.
// This is the resolution that the assets are optimized for.
float ideal_factor = 2;
FloatExtent ideal_resolution = { SCREEN_W * ideal_factor, SCREEN_H * ideal_factor };
if(largest_fullscreen_viewport.w == 0) {
// no way to determine the upper bound; guess it
largest_fullscreen_viewport = ideal_resolution;
}
IntExtent screenspace_max_size = coords_ext_pixels_to_screen(round_viewport_size(largest_fullscreen_viewport));
float scaling_factor = 0.5;
float scaling_factor_step = 0.25;
while(ideal_resolution.w * scaling_factor <= largest_fullscreen_viewport.w) {
FloatExtent vp = {
ideal_resolution.w * scaling_factor,
ideal_resolution.h * scaling_factor,
};
IntExtent pix_vp = round_viewport_size(vp);
video_add_mode_windowed(pix_vp, screenspace_min_size, screenspace_max_size);
scaling_factor += scaling_factor_step;
}
// Finally add the worst size we will tolerate, in case we haven't already.
video_add_mode_windowed((IntExtent) { VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT }, screenspace_min_size, screenspace_max_size);
}
dynarray_compact(&video.fs_modes);
dynarray_compact(&video.win_modes);
dynarray_qsort(&video.fs_modes, video_compare_modes);
dynarray_qsort(&video.win_modes, video_compare_modes);
if(!fullscreen_available) {
log_warn("No available fullscreen modes");
config_set_int(CONFIG_FULLSCREEN, false);
}
}
static void video_update_scaling_factor(void) {
// NOTE: must query the main framebuffer explicitly here; postprocess buffers may have outdated information.
IntExtent main_fb = r_framebuffer_get_size(NULL);
assert(main_fb.w > 0);
double scaling_factor = (double)main_fb.w / video.current.width;
if(scaling_factor != video.scaling_factor) {
log_debug("Scaling factor updated: %f --> %f", video.scaling_factor, scaling_factor);
video.scaling_factor = scaling_factor;
video_update_mode_lists();
2020-08-15 17:42:18 +02:00
IntExtent min_size = coords_ext_pixels_to_screen((IntExtent) { VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT });
SDL_SetWindowMinimumSize(video.window, min_size.w, min_size.h);
}
}
void video_get_viewport_size(float *width, float *height) {
IntExtent fb = video_get_screen_framebuffer_size();
FloatExtent vp = video_get_viewport_size_for_framebuffer(fb);
*width = vp.w;
*height = vp.h;
}
void video_get_viewport(FloatRect *vp) {
IntExtent fb = video_get_screen_framebuffer_size();
// vp->extent aliases vp->w and vp->h; see util/geometry.h
vp->extent = video_get_viewport_size_for_framebuffer(fb);
vp->x = (int)((fb.w - vp->w) * 0.5);
vp->y = (int)((fb.h - vp->h) * 0.5);
// This function can also be changed to return a FloatRect instead
log_debug("current w/h: %dx%d", video.current.width, video.current.height);
log_debug("viewport x/y: %fx%f", vp->x, vp->y);
log_debug("viewport w/h: %fx%f", vp->w, vp->h);
}
static void video_set_viewport(void) {
FloatRect vp;
video_get_viewport(&vp);
r_framebuffer_viewport_rect(NULL, vp);
}
2017-09-19 16:11:42 +02:00
static void video_update_vsync(void) {
if(global.frameskip || config_get_int(CONFIG_VSYNC) == 0) {
r_vsync(VSYNC_NONE);
} else {
switch(config_get_int(CONFIG_VSYNC)) {
case 1: r_vsync(VSYNC_NORMAL); break;
default: r_vsync(VSYNC_ADAPTIVE); break;
}
}
}
2017-03-05 03:22:41 +01:00
2017-09-19 14:13:04 +02:00
static void video_update_mode_settings(void) {
SDL_ShowCursor(!video_is_fullscreen());
2017-09-19 14:13:04 +02:00
video_update_vsync();
SDL_GetWindowSize(video.window, &video.current.width, &video.current.height);
2017-09-19 14:13:04 +02:00
video_set_viewport();
video_update_scaling_factor();
events_emit(TE_VIDEO_MODE_CHANGED, 0, NULL, NULL);
2017-09-19 14:13:04 +02:00
}
static const char *modeflagsstr(uint32_t flags) {
if(flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
return "fullscreen";
2017-09-19 14:13:04 +02:00
} else if(flags & SDL_WINDOW_RESIZABLE) {
return "windowed, resizable";
} else {
return "windowed";
}
}
static void video_new_window_internal(uint display, uint w, uint h, uint32_t flags, bool fallback) {
2017-02-04 03:56:40 +01:00
if(video.window) {
r_unclaim_window(video.window);
2017-02-04 03:56:40 +01:00
SDL_DestroyWindow(video.window);
video.window = NULL;
video.num_resize_events = 0;
2017-02-04 03:56:40 +01:00
}
2018-01-26 18:39:17 +01:00
char title[sizeof(WINDOW_TITLE) + strlen(TAISEI_VERSION) + 2];
2017-12-28 08:29:17 +01:00
snprintf(title, sizeof(title), "%s v%s", WINDOW_TITLE, TAISEI_VERSION);
video.window = r_create_window(
title,
SDL_WINDOWPOS_CENTERED_DISPLAY(display),
SDL_WINDOWPOS_CENTERED_DISPLAY(display),
w, h, flags | SDL_WINDOW_HIDDEN
);
2017-02-04 03:56:40 +01:00
if(video.window) {
SDL_ShowWindow(video.window);
2020-08-15 17:42:18 +02:00
if(video.scaling_factor != 0) {
IntExtent min_size = coords_ext_pixels_to_screen((IntExtent) { VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT });
SDL_SetWindowMinimumSize(video.window, min_size.w, min_size.h);
}
2017-09-19 14:13:04 +02:00
video_update_mode_settings();
2017-02-04 03:56:40 +01:00
return;
}
if(fallback) {
2017-09-19 14:13:04 +02:00
log_fatal("Failed to create window with mode %ix%i (%s): %s", w, h, modeflagsstr(flags), SDL_GetError());
2017-02-04 03:56:40 +01:00
return;
2012-07-28 22:53:53 +02:00
}
video_new_window_internal(display, RESX, RESY, flags & ~SDL_WINDOW_FULLSCREEN_DESKTOP, true);
2012-07-28 22:53:53 +02:00
}
static bool restrict_to_capability(bool enabled, VideoCapability cap) {
VideoCapabilityState capval = video_query_capability(cap);
switch(capval) {
case VIDEO_ALWAYS_ENABLED:
return true;
case VIDEO_NEVER_AVAILABLE:
return false;
default:
return enabled;
}
}
static void video_new_window(uint display, uint w, uint h, bool fs, bool resizable) {
uint32_t flags = SDL_WINDOW_ALLOW_HIGHDPI;
fs = restrict_to_capability(fs, VIDEO_CAP_FULLSCREEN);
resizable = restrict_to_capability(resizable, VIDEO_CAP_EXTERNAL_RESIZE);
if(fs) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else if(resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
video_new_window_internal(display, w, h, flags, false);
display = video_current_display();
log_info("Created a new window: %ix%i (%s), on display #%i %s",
video.current.width,
video.current.height,
modeflagsstr(SDL_GetWindowFlags(video.window)),
display,
video_display_name(display)
);
events_pause_keyrepeat();
SDL_RaiseWindow(video.window);
2012-07-28 22:53:53 +02:00
}
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
static void video_set_fullscreen_internal(bool fullscreen) {
uint32_t flags = fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
events_pause_keyrepeat();
2017-09-19 16:11:42 +02:00
if(SDL_SetWindowFullscreen(video.window, flags) < 0) {
log_error("Failed to switch to %s mode: %s", modeflagsstr(flags), SDL_GetError());
2017-09-19 16:11:42 +02:00
}
SDL_RaiseWindow(video.window);
2017-09-19 16:11:42 +02:00
}
INLINE bool should_recreate_on_size_change(void) {
bool defaultval = (
/* Resize failures are impossible to detect under some WMs */
video.backend == VIDEO_BACKEND_X11 ||
/* Needed to work around various SDL bugs and/or HTML/DOM quirks */
// video.backend == VIDEO_BACKEND_EMSCRIPTEN ||
0);
return env_get("TAISEI_VIDEO_RECREATE_ON_RESIZE", defaultval);
}
INLINE bool should_recreate_on_fullscreen_change(void) {
bool defaultval = (
/* FIXME Do we need this? */
video.backend == VIDEO_BACKEND_X11 ||
0);
return env_get("TAISEI_VIDEO_RECREATE_ON_FULLSCREEN", defaultval);
}
void video_set_mode(uint display, uint w, uint h, bool fs, bool resizable) {
fs = restrict_to_capability(fs, VIDEO_CAP_FULLSCREEN);
resizable = restrict_to_capability(resizable, VIDEO_CAP_EXTERNAL_RESIZE);
2017-09-19 14:13:04 +02:00
video.intended.width = w;
video.intended.height = h;
if(display >= video_num_displays()) {
log_warn("Display index %u is invalid, falling back to 0 (%s)", display, video_display_name(0));
display = 0;
}
2017-09-19 14:13:04 +02:00
if(!video.window) {
video_new_window(display, w, h, fs, resizable);
return;
}
if(
!restrict_to_capability(true, VIDEO_CAP_CHANGE_RESOLUTION) &&
video.current.width > 0 && video.current.height > 0
) {
w = video.current.width;
h = video.current.height;
}
bool display_changed = display != video_current_display();
bool size_changed = w != video.current.width || h != video.current.height;
bool fullscreen_changed = video_is_fullscreen() != fs;
if(display_changed) {
video_new_window(display, w, h, fs, resizable);
2017-09-19 14:13:04 +02:00
return;
}
if(fullscreen_changed && should_recreate_on_fullscreen_change()) {
video_new_window(display, w, h, fs, resizable);
return;
}
if(size_changed && !fs) {
if(!fullscreen_changed && should_recreate_on_size_change()) {
video_new_window(display, w, h, fs, resizable);
2017-09-19 14:13:04 +02:00
return;
}
SDL_SetWindowSize(video.window, w, h);
SDL_SetWindowPosition(
video.window,
SDL_WINDOWPOS_CENTERED_DISPLAY(display),
SDL_WINDOWPOS_CENTERED_DISPLAY(display)
);
2017-09-19 14:13:04 +02:00
}
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
video_set_fullscreen_internal(fs);
2017-09-19 14:13:04 +02:00
SDL_SetWindowResizable(video.window, resizable);
if(size_changed) {
video_update_mode_settings();
}
2017-09-19 14:13:04 +02:00
}
SDL_Window *video_get_window(void) {
return video.window;
}
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
void video_set_fullscreen(bool fullscreen) {
if(video_query_capability(VIDEO_CAP_FULLSCREEN) != VIDEO_AVAILABLE) {
return;
}
video_set_mode(
SDL_GetWindowDisplayIndex(video.window),
video.intended.width,
video.intended.height,
fullscreen,
config_get_int(CONFIG_VID_RESIZABLE)
);
}
void video_set_display(uint idx) {
video_set_mode(
idx,
video.intended.width,
video.intended.height,
config_get_int(CONFIG_FULLSCREEN),
config_get_int(CONFIG_VID_RESIZABLE)
);
Emscripten compatibility (#161) * Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
}
static void *video_screenshot_task(void *arg) {
ScreenshotTaskData *tdata = arg;
OpenGL ES 3.0 rendering backend (#148) * First steps towards shader transpilation Needs to be manually enabled via -Dshader_transpiler=true. Requires shaderc. https://github.com/google/shaderc Not yet functional due to missing SPIRV-Cross integration. SPIRV-Cross currently does not have an official C API, and crossc is too minimal to be useful. The current plan is to extend crossc and vendor it, while also sending PRs upstream. * Integrate crossc; shader transpilation for GLES now works * fix leak * gles30 backend now playable on Mesa with 3.2 context Some rendering issues are present. Identified so far: - Marisa's lasers are invisible - Death effect looks wrong Also, a small pixmap manipulation library has been written, and the texture uploading API redesigned around it. * fix marisa lasers in GLES (uniform name clashed with builtin) * fix player death effect in GLES (another name clash) * Dump ANGLE's translated shader code in debug log * fix screenshots * Drop support for triangle fans, switch to strips Fans offer no advantage over strips, and they've been removed in D3D10+, so ANGLE has to emulate them. * crude workaround for an ANGLE bug * Re-enable GL debug labels, fix an issue with them that affected ANGLE (but was always technically a bug) * fix race condition in shaderc initialization * New SDL_RWops interface for vertex buffers * Optimize VBO streaming via buffering updates Measurable performance improvement even with the main gl33 renderer, drastic improvement with ANGLE. * Fix the depth texture binding problem under ANGLE Apparently it hates GL_DEPTH_COMPONENT16 for some reason. Sized internal formats are not supported in GLES 2.0 anyway, so not using them is probably a good idea. * fix GLES2.0 segfault (the backend still doesn't work, though) * dump GL extensions at info log level, not debug * get around a Mesa bug; more correct texture format table for GLES2 * Correct GLES3 texture format table according to the spec Not a Mesa bug after all * require crossc>=1.5.0, fallback to subproject * Request at least 8bit per color channel in GL backends * Forbid lto for static windows builds with shader_transpiler=true * fix edge case segfault * Add basic ANGLE bundling support to the build system Windows only, and no NSIS support yet * Fix various windows-related build system and installer brokenness * Disable gles backends by default * update documentation
2018-10-02 00:36:10 +02:00
pixmap_convert_inplace_realloc(&tdata->image, PIXMAP_FORMAT_RGB8);
PixmapPNGSaveOptions opts = PIXMAP_DEFAULT_PNG_SAVE_OPTIONS;
if(tdata->dest_path) {
opts.zlib_compression_level = 9;
bool ok = pixmap_save_file(tdata->dest_path, &tdata->image, &opts.base);
if(LIKELY(ok)) {