From 54237fc7a1981913ceef3503ef6ca6cdf2749ec1 Mon Sep 17 00:00:00 2001 From: Joep Vanlier Date: Sun, 9 Feb 2025 15:18:18 +0100 Subject: [PATCH] ysfx/plugin: support `options: gfx_hz=#` --- exports/ysfx.txt | 1 + include/ysfx.h | 2 ++ plugin/components/graphics_view.cpp | 2 +- plugin/components/tokenizer_functions.h | 2 +- sources/ysfx.cpp | 7 +++++++ sources/ysfx_parse.cpp | 10 ++++++++-- sources/ysfx_parse.hpp | 1 + tests/ysfx_test_integration.cpp | 24 ++++++++++++++++++++++++ 8 files changed, 45 insertions(+), 4 deletions(-) diff --git a/exports/ysfx.txt b/exports/ysfx.txt index 6cdc9220..4848d325 100644 --- a/exports/ysfx.txt +++ b/exports/ysfx.txt @@ -106,5 +106,6 @@ ysfx_gfx_wants_retina ysfx_gfx_add_key ysfx_gfx_update_mouse ysfx_gfx_run +ysfx_get_requested_framerate ysfx_parse_menu ysfx_menu_free diff --git a/include/ysfx.h b/include/ysfx.h index 91c73445..86f60e7b 100644 --- a/include/ysfx.h +++ b/include/ysfx.h @@ -473,6 +473,8 @@ YSFX_API void ysfx_gfx_add_key(ysfx_t *fx, uint32_t mods, uint32_t key, bool pre YSFX_API void ysfx_gfx_update_mouse(ysfx_t *fx, uint32_t mods, int32_t xpos, int32_t ypos, uint32_t buttons, ysfx_real wheel, ysfx_real hwheel); // invoke @gfx to paint the graphics; returns whether the framer buffer is modified YSFX_API bool ysfx_gfx_run(ysfx_t *fx); +// request desired frame rate for UI refresh +YSFX_API uint32_t ysfx_get_requested_framerate(ysfx_t *fx); //------------------------------------------------------------------------------ // YSFX key map diff --git a/plugin/components/graphics_view.cpp b/plugin/components/graphics_view.cpp index 7b1519c4..72e04f62 100644 --- a/plugin/components/graphics_view.cpp +++ b/plugin/components/graphics_view.cpp @@ -240,7 +240,7 @@ void YsfxGraphicsView::setEffect(ysfx_t *fx) else { m_impl->m_work.start(); m_impl->m_gfxTimer.reset(FunctionalTimer::create([this]() { m_impl->tickGfx(); })); - m_impl->m_gfxTimer->startTimerHz(30); + m_impl->m_gfxTimer->startTimerHz(ysfx_get_requested_framerate(fx)); } m_impl->m_gfxInputState.reset(new Impl::GfxInputState); diff --git a/plugin/components/tokenizer_functions.h b/plugin/components/tokenizer_functions.h index 8bde6c04..3a411950 100644 --- a/plugin/components/tokenizer_functions.h +++ b/plugin/components/tokenizer_functions.h @@ -14,7 +14,7 @@ namespace JSFXTokenizerFunctions { static const char* const keywords4Char[] = { "@gfx", "desc", "tags", nullptr }; static const char* const keywords5Char[] = { "@init", nullptr }; static const char* const keywords6Char[] = { "@block", "import", "in_pin", nullptr }; - static const char* const keywords7Char[] = { "@sample", "@slider", "out_pin", nullptr }; + static const char* const keywords7Char[] = { "@sample", "@slider", "out_pin", "options", nullptr }; static const char* const keywords8Char[] = { nullptr }; static const char* const keywords9Char[] = { nullptr }; static const char* const keywords10Char[] = { "@serialize", nullptr }; diff --git a/sources/ysfx.cpp b/sources/ysfx.cpp index 15031686..0c43748d 100644 --- a/sources/ysfx.cpp +++ b/sources/ysfx.cpp @@ -1395,6 +1395,13 @@ bool ysfx_fetch_want_undopoint(ysfx_t *fx) return undo_point; } +uint32_t ysfx_get_requested_framerate(ysfx_t *fx) +{ + if (!fx || !ysfx_is_compiled(fx)) return 30; + + return fx->source.main->header.options.gfx_hz; +} + uint64_t ysfx_fetch_slider_touches(ysfx_t *fx, uint8_t slider_group_index) { return fx->slider.touch_mask[slider_group_index].load(); diff --git a/sources/ysfx_parse.cpp b/sources/ysfx_parse.cpp index 3d6a1765..c75fcecf 100644 --- a/sources/ysfx_parse.cpp +++ b/sources/ysfx_parse.cpp @@ -163,10 +163,16 @@ void ysfx_parse_header(ysfx_section_t *section, ysfx_header_t &header) int32_t prealloc = (value.compare("*") == 0) ? -1 : (int32_t) ysfx::dot_atof(value.c_str()); header.options.prealloc = prealloc; } - else if (name == "want_all_kb") + else if (name == "want_all_kb") { header.options.want_all_kb = true; - else if (name == "no_meter") + } else if (name == "no_meter") { header.options.no_meter = true; + } else if (name == "gfx_hz") { + int32_t gfx_hz = (int32_t) ysfx::dot_atof(value.c_str()); + if (gfx_hz > 0) { + header.options.gfx_hz = static_cast(gfx_hz); + } + } } } else if (unprefix(linep, &rest, "import") && ysfx::ascii_isspace(rest[0])) diff --git a/sources/ysfx_parse.hpp b/sources/ysfx_parse.hpp index bd4ee0b2..f3114c75 100644 --- a/sources/ysfx_parse.hpp +++ b/sources/ysfx_parse.hpp @@ -87,6 +87,7 @@ struct ysfx_options_t { int32_t prealloc = 0; bool want_all_kb = false; bool no_meter = false; + uint32_t gfx_hz = 30; }; struct ysfx_header_t { diff --git a/tests/ysfx_test_integration.cpp b/tests/ysfx_test_integration.cpp index 4e10edbb..662197ee 100644 --- a/tests/ysfx_test_integration.cpp +++ b/tests/ysfx_test_integration.cpp @@ -147,6 +147,30 @@ TEST_CASE("integration", "[integration]") REQUIRE(ysfx_read_vmem_single(fx.get(), 33554432) == 6); }; + SECTION("gfx_hz") + { + auto compile_and_check = [](const char *text, uint32_t ref_value) { + scoped_new_dir dir_fx("${root}/Effects"); + scoped_new_txt file_main("${root}/Effects/example.jsfx", text); + + ysfx_config_u config{ysfx_config_new()}; + ysfx_u fx{ysfx_new(config.get())}; + + REQUIRE(ysfx_load_file(fx.get(), file_main.m_path.c_str(), 0)); + REQUIRE(ysfx_compile(fx.get(), 0)); + + REQUIRE(ysfx_get_requested_framerate(fx.get()) == ref_value); + }; + + compile_and_check("desc:test" "\noptions:gfx_hz=30\nout_pin:output\n@init\n", 30); + compile_and_check("desc:test" "\noptions:gfx_hz=60\nout_pin:output\n@init\n", 60); + compile_and_check("desc:test" "\noptions:gfx_hz=120\nout_pin:output\n@init\n", 120); + compile_and_check("desc:test" "\noptions:gfx_hz=-1\nout_pin:output\n@init\n", 30); + compile_and_check("desc:test" "\noptions:gfx_hz=45334954317053419571340971349057134051345\nout_pin:output\n@init\n", 30); + compile_and_check("desc:test" "\noptions:gfx_hz=invalid\nout_pin:output\n@init\n", 30); + compile_and_check("desc:test" "\nout_pin:output\n@init\n", 30); + } + SECTION("pre_alloc none") { const char *text =