2012-07-28 22:53:53 +02:00
|
|
|
/*
|
2019-08-03 19:43:48 +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"
|
2024-05-17 04:41:28 +02:00
|
|
|
|
|
|
|
#include "dynarray.h"
|
|
|
|
#include "events.h"
|
|
|
|
#include "global.h"
|
2018-04-12 16:08:48 +02:00
|
|
|
#include "renderer/api.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "rwops/rwops_autobuf.h"
|
|
|
|
#include "stagedraw.h"
|
|
|
|
#include "taskmanager.h"
|
|
|
|
#include "util/env.h"
|
2019-08-25 01:28:46 +02:00
|
|
|
#include "util/fbmgr.h"
|
2023-07-30 06:13:09 +02:00
|
|
|
#include "util/graphics.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "util/io.h"
|
2021-05-30 03:26:21 +02:00
|
|
|
#include "version.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "video_postprocess.h"
|
2017-02-24 01:54:28 +01:00
|
|
|
|
2020-04-05 04:51:00 +02:00
|
|
|
typedef DYNAMIC_ARRAY(VideoMode) VideoModeArray;
|
2020-03-29 23:03:38 +02:00
|
|
|
|
2024-04-29 00:57:20 +02:00
|
|
|
typedef enum FramedumpSource {
|
|
|
|
FRAMEDUMP_SRC_SCREEN,
|
|
|
|
FRAMEDUMP_SRC_VIEWPORT,
|
|
|
|
} FramedumpSource;
|
|
|
|
|
2019-09-11 17:42:13 +02:00
|
|
|
static struct {
|
2020-03-29 23:03:38 +02:00
|
|
|
VideoModeArray fs_modes;
|
|
|
|
VideoModeArray win_modes;
|
2019-09-11 17:42:13 +02:00
|
|
|
SDL_Window *window;
|
2020-04-16 18:25:54 +02:00
|
|
|
VideoPostProcess *postprocess;
|
2019-09-11 17:42:13 +02:00
|
|
|
VideoMode intended;
|
|
|
|
VideoMode current;
|
|
|
|
VideoBackend backend;
|
2020-04-16 18:25:54 +02:00
|
|
|
double scaling_factor;
|
2023-06-23 10:29:34 +02:00
|
|
|
uint num_resize_events;
|
2024-04-29 00:57:20 +02:00
|
|
|
|
|
|
|
struct {
|
|
|
|
char *name_prefix;
|
|
|
|
size_t name_prefix_len;
|
|
|
|
size_t frame_count;
|
|
|
|
int compression;
|
|
|
|
FramedumpSource source;
|
|
|
|
} framedump;
|
2019-09-11 17:42:13 +02:00
|
|
|
} video;
|
2012-07-28 22:53:53 +02:00
|
|
|
|
2024-04-29 00:57:20 +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
|
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
VideoCapabilityState (*video_query_capability)(VideoCapability cap);
|
|
|
|
|
2018-05-25 08:01:07 +02:00
|
|
|
typedef struct ScreenshotTaskData {
|
2024-04-29 00:57:20 +02:00
|
|
|
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;
|
2024-04-29 00:57:20 +02:00
|
|
|
uint32_t frame_num; // for framedump mode only
|
2018-05-25 08:01:07 +02:00
|
|
|
} ScreenshotTaskData;
|
2012-07-28 22:53:53 +02:00
|
|
|
|
2020-04-16 18:25:54 +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)
|
2019-08-09 20:42:04 +02:00
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
/*
|
|
|
|
* 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:
|
2020-04-16 18:25:54 +02:00
|
|
|
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:
|
2024-10-24 01:08:32 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-04-05 04:51:00 +02:00
|
|
|
for(uint i = 0; i < mode_array->num_elements; ++i) {
|
|
|
|
VideoMode *m = mode_array->data + i;
|
2018-07-04 10:36:16 +02:00
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
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);
|
2020-04-05 04:51:00 +02:00
|
|
|
return;
|
2012-07-28 22:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2023-09-23 13:49:26 +02:00
|
|
|
dynarray_append(mode_array, { .as_int_extent = mode_screen });
|
2020-04-16 18:25:54 +02:00
|
|
|
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);
|
2020-03-29 23:03:38 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
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");
|
2020-03-29 23:03:38 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
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) {
|
2020-04-16 18:25:54 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-03-30 00:41:29 +02:00
|
|
|
static IntExtent video_get_screen_framebuffer_size(void) {
|
2020-04-23 23:11:54 +02:00
|
|
|
return r_framebuffer_get_size(NULL);
|
2020-03-29 23:03:38 +02:00
|
|
|
}
|
|
|
|
|
2020-03-30 00:41:29 +02:00
|
|
|
static FloatExtent video_get_viewport_size_for_framebuffer(IntExtent framebuffer_size) {
|
2020-03-29 23:03:38 +02:00
|
|
|
float w = framebuffer_size.w;
|
|
|
|
float h = framebuffer_size.h;
|
2017-02-04 04:58:55 +01:00
|
|
|
float r = w / h;
|
|
|
|
|
2017-04-08 20:48:40 +02:00
|
|
|
if(r > VIDEO_ASPECT_RATIO) {
|
|
|
|
w = h * VIDEO_ASPECT_RATIO;
|
|
|
|
} else if(r < VIDEO_ASPECT_RATIO) {
|
|
|
|
h = w / VIDEO_ASPECT_RATIO;
|
2017-02-04 04:58:55 +01:00
|
|
|
}
|
2018-04-12 16:08:48 +02:00
|
|
|
|
2020-03-29 23:03:38 +02:00
|
|
|
return (FloatExtent) { w, h };
|
|
|
|
}
|
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
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;
|
2021-04-13 19:26:23 +02:00
|
|
|
float scaling_factor_step = 0.25;
|
2020-04-16 18:25:54 +02:00
|
|
|
|
|
|
|
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);
|
2020-04-16 18:25:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 23:03:38 +02:00
|
|
|
void video_get_viewport_size(float *width, float *height) {
|
2020-03-30 00:41:29 +02:00
|
|
|
IntExtent fb = video_get_screen_framebuffer_size();
|
2020-03-29 23:03:38 +02:00
|
|
|
FloatExtent vp = video_get_viewport_size_for_framebuffer(fb);
|
|
|
|
|
|
|
|
*width = vp.w;
|
|
|
|
*height = vp.h;
|
2017-04-19 09:56:18 +02:00
|
|
|
}
|
|
|
|
|
2019-04-04 01:43:03 +02:00
|
|
|
void video_get_viewport(FloatRect *vp) {
|
2020-03-30 00:41:29 +02:00
|
|
|
IntExtent fb = video_get_screen_framebuffer_size();
|
2020-03-29 23:03:38 +02:00
|
|
|
|
2020-03-30 00:41:29 +02:00
|
|
|
// vp->extent aliases vp->w and vp->h; see util/geometry.h
|
2020-03-29 23:03:38 +02:00
|
|
|
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);
|
2018-07-04 10:36:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void video_set_viewport(void) {
|
2019-04-04 01:43:03 +02:00
|
|
|
FloatRect vp;
|
2018-07-04 10:36:16 +02:00
|
|
|
video_get_viewport(&vp);
|
|
|
|
r_framebuffer_viewport_rect(NULL, vp);
|
2017-02-04 04:58:55 +01:00
|
|
|
}
|
|
|
|
|
2017-09-19 16:11:42 +02:00
|
|
|
static void video_update_vsync(void) {
|
2017-09-26 23:55:28 +02:00
|
|
|
if(global.frameskip || config_get_int(CONFIG_VSYNC) == 0) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_vsync(VSYNC_NONE);
|
2017-02-10 02:11:45 +01:00
|
|
|
} else {
|
2018-04-12 16:08:48 +02:00
|
|
|
switch(config_get_int(CONFIG_VSYNC)) {
|
|
|
|
case 1: r_vsync(VSYNC_NORMAL); break;
|
|
|
|
default: r_vsync(VSYNC_ADAPTIVE); break;
|
2017-02-10 02:11:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-05 03:22:41 +01:00
|
|
|
|
2017-09-19 14:13:04 +02:00
|
|
|
static void video_update_mode_settings(void) {
|
2023-01-27 23:44:04 +01:00
|
|
|
SDL_ShowCursor(!video_is_fullscreen());
|
2017-09-19 14:13:04 +02:00
|
|
|
video_update_vsync();
|
2018-04-12 16:08:48 +02:00
|
|
|
SDL_GetWindowSize(video.window, &video.current.width, &video.current.height);
|
2017-09-19 14:13:04 +02:00
|
|
|
video_set_viewport();
|
2023-06-23 09:54:51 +02:00
|
|
|
video_update_scaling_factor();
|
2017-09-29 21:03:49 +02:00
|
|
|
events_emit(TE_VIDEO_MODE_CHANGED, 0, NULL, NULL);
|
2017-09-19 14:13:04 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 18:25:54 +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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
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) {
|
2024-07-22 00:33:43 +02:00
|
|
|
r_unclaim_window(video.window);
|
2017-02-04 03:56:40 +01:00
|
|
|
SDL_DestroyWindow(video.window);
|
|
|
|
video.window = NULL;
|
2023-06-23 10:29:34 +02:00
|
|
|
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);
|
2019-04-18 21:56:16 +02:00
|
|
|
video.window = r_create_window(
|
|
|
|
title,
|
|
|
|
SDL_WINDOWPOS_CENTERED_DISPLAY(display),
|
|
|
|
SDL_WINDOWPOS_CENTERED_DISPLAY(display),
|
2019-09-21 15:39:05 +02:00
|
|
|
w, h, flags | SDL_WINDOW_HIDDEN
|
2019-04-18 21:56:16 +02:00
|
|
|
);
|
2017-02-04 03:56:40 +01:00
|
|
|
|
|
|
|
if(video.window) {
|
2019-09-21 15:39:05 +02:00
|
|
|
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
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
video_new_window_internal(display, RESX, RESY, flags & ~SDL_WINDOW_FULLSCREEN_DESKTOP, true);
|
2012-07-28 22:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-10-24 01:08:32 +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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
static void video_new_window(uint display, uint w, uint h, bool fs, bool resizable) {
|
2020-04-16 18:25:54 +02:00
|
|
|
uint32_t flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
2017-02-04 05:08:34 +01:00
|
|
|
|
2024-10-24 01:08:32 +02:00
|
|
|
fs = restrict_to_capability(fs, VIDEO_CAP_FULLSCREEN);
|
|
|
|
resizable = restrict_to_capability(resizable, VIDEO_CAP_EXTERNAL_RESIZE);
|
|
|
|
|
|
|
|
if(fs) {
|
2020-04-16 18:25:54 +02:00
|
|
|
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
2024-10-24 01:08:32 +02:00
|
|
|
} else if(resizable) {
|
2017-03-01 01:25:52 +01:00
|
|
|
flags |= SDL_WINDOW_RESIZABLE;
|
|
|
|
}
|
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
video_new_window_internal(display, w, h, flags, false);
|
|
|
|
display = video_current_display();
|
2017-03-20 07:08:09 +01:00
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
log_info("Created a new window: %ix%i (%s), on display #%i %s",
|
2017-03-20 07:08:09 +01:00
|
|
|
video.current.width,
|
|
|
|
video.current.height,
|
2019-04-18 21:56:16 +02:00
|
|
|
modeflagsstr(SDL_GetWindowFlags(video.window)),
|
|
|
|
display,
|
|
|
|
video_display_name(display)
|
2017-03-20 07:08:09 +01:00
|
|
|
);
|
2017-11-11 19:47:15 +01:00
|
|
|
|
|
|
|
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) {
|
2020-04-16 18:25:54 +02:00
|
|
|
uint32_t flags = fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
|
2017-11-11 19:47:15 +01:00
|
|
|
events_pause_keyrepeat();
|
2017-09-19 16:11:42 +02:00
|
|
|
|
2017-11-11 19:47:15 +01:00
|
|
|
if(SDL_SetWindowFullscreen(video.window, flags) < 0) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("Failed to switch to %s mode: %s", modeflagsstr(flags), SDL_GetError());
|
2017-09-19 16:11:42 +02:00
|
|
|
}
|
2017-11-11 19:47:15 +01:00
|
|
|
|
|
|
|
SDL_RaiseWindow(video.window);
|
2017-09-19 16:11:42 +02:00
|
|
|
}
|
|
|
|
|
2023-02-23 01:43:43 +01: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 */
|
2024-10-24 01:08:32 +02:00
|
|
|
// video.backend == VIDEO_BACKEND_EMSCRIPTEN ||
|
2023-02-23 01:43:43 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-06-20 00:48:40 +02:00
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
void video_set_mode(uint display, uint w, uint h, bool fs, bool resizable) {
|
2023-06-20 00:48:40 +02:00
|
|
|
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;
|
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
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) {
|
2019-04-18 21:56:16 +02:00
|
|
|
video_new_window(display, w, h, fs, resizable);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-24 01:08:32 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-02-23 01:43:43 +01:00
|
|
|
bool display_changed = display != video_current_display();
|
2019-04-18 21:56:16 +02:00
|
|
|
bool size_changed = w != video.current.width || h != video.current.height;
|
2023-02-23 01:43:43 +01:00
|
|
|
bool fullscreen_changed = video_is_fullscreen() != fs;
|
2019-04-18 21:56:16 +02:00
|
|
|
|
|
|
|
if(display_changed) {
|
|
|
|
video_new_window(display, w, h, fs, resizable);
|
2017-09-19 14:13:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-02-23 01:43:43 +01:00
|
|
|
if(fullscreen_changed && should_recreate_on_fullscreen_change()) {
|
|
|
|
video_new_window(display, w, h, fs, resizable);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-24 01:08:32 +02:00
|
|
|
if(size_changed && !fs) {
|
2023-02-23 01:43:43 +01:00
|
|
|
if(!fullscreen_changed && should_recreate_on_size_change()) {
|
2019-04-18 21:56:16 +02:00
|
|
|
video_new_window(display, w, h, fs, resizable);
|
2017-09-19 14:13:04 +02:00
|
|
|
return;
|
|
|
|
}
|
2023-02-23 01:43:43 +01:00
|
|
|
|
|
|
|
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);
|
2019-09-11 18:19:44 +02:00
|
|
|
|
|
|
|
if(size_changed) {
|
|
|
|
video_update_mode_settings();
|
|
|
|
}
|
2017-09-19 14:13:04 +02:00
|
|
|
}
|
|
|
|
|
2021-07-03 15:29:24 +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) {
|
2024-10-24 01:08:32 +02:00
|
|
|
if(video_query_capability(VIDEO_CAP_FULLSCREEN) != VIDEO_AVAILABLE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-18 21:56:16 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-04-16 18:25:54 +02:00
|
|
|
static void *video_screenshot_task(void *arg) {
|
2018-05-25 08:01:07 +02:00
|
|
|
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);
|
2018-05-22 07:53:18 +02:00
|
|
|
|
2021-07-18 17:19:40 +02:00
|
|
|
PixmapPNGSaveOptions opts = PIXMAP_DEFAULT_PNG_SAVE_OPTIONS;
|
2017-03-06 01:25:59 +01:00
|
|
|
|
2024-04-29 00:57:20 +02:00
|
|
|
if(tdata->dest_path) {
|
|
|
|
opts.zlib_compression_level = 9;
|
|
|
|
|
|
|
|
bool ok = pixmap_save_file(tdata->dest_path, &tdata->image, &opts.base);
|
|
|
|
|
|
|
|
if(LIKELY(ok)) {
|
|
|