diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a1d18231..8ae39667b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,32 +75,35 @@ jobs: fail-fast: false matrix: # MacOS 13 runs on Intel, 14 runs on ARM - os: [ubuntu-latest, macos-13, macos-14] + os: [macos-13, macos-14, ubuntu-latest] runs-on: ${{ matrix.os }} - name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test + name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test timeout-minutes: 15 steps: - - uses: actions/checkout@v4 - - name: Download LLVM (Linux) - if: matrix.os == 'ubuntu-latest' - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 20 - echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH + - uses: actions/checkout@v4 - name: Download LLVM (MacOS Intel) if: matrix.os == 'macos-13' run: | brew update brew install llvm@20 lua@5.4 lld + echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH - name: Download LLVM (MacOS ARM) if: matrix.os == 'macos-14' run: | brew update brew install llvm@20 wasmtime lua@5.4 lld + echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH + + - name: Download LLVM (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 20 + echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH - name: Build Odin run: ./build_odin.sh release @@ -124,53 +127,52 @@ jobs: - name: Odin check vendor/sdl3 run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point - name: Normal Core library tests - run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Optimized Core library tests - run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Vendor library tests - run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Internals tests - run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: GitHub Issue tests run: | cd tests/issues ./run.sh - - name: Check benchmarks - run: ./odin check tests/benchmark -vet -strict-style -no-entry-point - - name: Odin check examples/all for Linux i386 - run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386 - if: matrix.os == 'ubuntu-latest' - - name: Odin check examples/all for Linux arm64 - run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64 - if: matrix.os == 'ubuntu-latest' - - name: Odin check examples/all for FreeBSD amd64 - run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 - if: matrix.os == 'ubuntu-latest' - - name: Odin check examples/all for OpenBSD amd64 - run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64 - if: matrix.os == 'ubuntu-latest' - - - name: Odin check vendor/sdl3 for Linux i386 - run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386 - if: matrix.os == 'ubuntu-latest' - - name: Odin check vendor/sdl3 for Linux arm64 - run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64 - if: matrix.os == 'ubuntu-latest' - - name: Odin check vendor/sdl3 for FreeBSD amd64 - run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64 - if: matrix.os == 'ubuntu-latest' - - name: Odin check vendor/sdl3 for OpenBSD amd64 - run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64 - if: matrix.os == 'ubuntu-latest' - - - name: Run demo on WASI WASM32 run: | ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo wasmtime ./demo.wasm if: matrix.os == 'macos-14' + - name: Check benchmarks + run: ./odin check tests/benchmark -vet -strict-style -no-entry-point + - name: Odin check examples/all for Linux i386 + if: matrix.os == 'ubuntu-latest' + run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386 + - name: Odin check examples/all for Linux arm64 + if: matrix.os == 'ubuntu-latest' + run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64 + - name: Odin check examples/all for FreeBSD amd64 + if: matrix.os == 'ubuntu-latest' + run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 + - name: Odin check examples/all for OpenBSD amd64 + if: matrix.os == 'ubuntu-latest' + run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64 + + - name: Odin check vendor/sdl3 for Linux i386 + if: matrix.os == 'ubuntu-latest' + run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386 + - name: Odin check vendor/sdl3 for Linux arm64 + if: matrix.os == 'ubuntu-latest' + run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64 + - name: Odin check vendor/sdl3 for FreeBSD amd64 + if: matrix.os == 'ubuntu-latest' + run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64 + - name: Odin check vendor/sdl3 for OpenBSD amd64 + if: matrix.os == 'ubuntu-latest' + run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64 + build_windows: name: Windows Build, Check, and Test runs-on: windows-2022 @@ -215,23 +217,23 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Optimized core library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Vendor library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat copy vendor\lua\5.4\windows\*.dll . - odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Odin internals tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address - name: Check issues shell: cmd run: | diff --git a/.gitignore b/.gitignore index 32e5f5b0f..1187596de 100644 --- a/.gitignore +++ b/.gitignore @@ -293,5 +293,6 @@ build.sh # RAD debugger project file *.raddbg - +*.rdi +tests/issues/build/* misc/featuregen/featuregen diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin index 227ceeb49..14da9603d 100644 --- a/base/builtin/builtin.odin +++ b/base/builtin/builtin.odin @@ -119,7 +119,8 @@ jmag :: proc(value: Quaternion) -> Float --- kmag :: proc(value: Quaternion) -> Float --- conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion --- -expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) --- +expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) --- +compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type --- min :: proc(values: ..T) -> T --- max :: proc(values: ..T) -> T --- diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 9429ec023..46e39c8d1 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -221,6 +221,9 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info --- type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) --- type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) --- +type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) --- +type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) --- + type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) --- constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- @@ -357,15 +360,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- objc_object :: struct{} objc_selector :: struct{} objc_class :: struct{} +objc_ivar :: struct{} + objc_id :: ^objc_object objc_SEL :: ^objc_selector objc_Class :: ^objc_class +objc_Ivar :: ^objc_ivar objc_find_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL --- objc_find_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class --- - +objc_ivar_get :: proc(self: ^$T) -> ^$U --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index bff5b8380..38b7f662c 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1109,6 +1109,7 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin when .Address in ODIN_SANITIZER_FLAGS { foreign { + @(require) __asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) --- } } diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index c3fc46af1..0aec57e80 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -2,21 +2,34 @@ package runtime @(priority_index=-1e6) -foreign import "system:Foundation.framework" +foreign import ObjC "system:objc" import "base:intrinsics" -objc_id :: ^intrinsics.objc_object +objc_id :: ^intrinsics.objc_object objc_Class :: ^intrinsics.objc_class -objc_SEL :: ^intrinsics.objc_selector +objc_SEL :: ^intrinsics.objc_selector +objc_Ivar :: ^intrinsics.objc_ivar +objc_BOOL :: bool -foreign Foundation { - objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- + +objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id + +foreign ObjC { sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- - objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + + objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- + objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- + objc_registerClassPair :: proc "c" (cls : objc_Class) --- + class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL --- + class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL --- + class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar --- + class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint --- + ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr --- } + diff --git a/build.bat b/build.bat index ae733ff2a..b1ff9b173 100644 --- a/build.bat +++ b/build.bat @@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" ( ) ) +where /Q git.exe || goto skip_git_hash +if not exist .git\ goto skip_git_hash +for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do ( + set CURR_DATE_TIME=%%i + set GIT_SHA=%%j +) +if %ERRORLEVEL% equ 0 ( + goto have_git_hash_and_date +) +:skip_git_hash pushd misc cl /nologo get-date.c -popd - -for /f %%i in ('misc\get-date') do ( +for /f %%i in ('get-date') do ( set CURR_DATE_TIME=%%i + rem Don't set GIT_SHA ) +popd +:have_git_hash_and_date set curr_year=%CURR_DATE_TIME:~0,4% -set curr_month=%CURR_DATE_TIME:~4,2% -set curr_day=%CURR_DATE_TIME:~6,2% +set curr_month=%CURR_DATE_TIME:~5,2% +set curr_day=%CURR_DATE_TIME:~8,2% :: Make sure this is a decent name and not generic set exe_name=odin.exe @@ -61,31 +72,14 @@ if %release_mode% equ 0 ( set V4=0 set odin_version_full="%V1%.%V2%.%V3%.%V4%" set odin_version_raw="dev-%V1%-%V2%" - set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF rem Parse source code as utf-8 even on shift-jis and other codepages rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 set compiler_flags= %compiler_flags% /utf-8 -set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" +set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\" rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited -set rc_flags=-nologo ^ --DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^ --DVF=%odin_version_full% -DNIGHTLY=%nightly% - -where /Q git.exe || goto skip_git_hash -if not exist .git\ goto skip_git_hash -for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do ( - set odin_version_raw=dev-%%i - set GIT_SHA=%%j -) -if %ERRORLEVEL% equ 0 ( - set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\" - set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA% -) else ( - set rc_flags=%rc_flags% -DVP=%odin_version_raw% -) -:skip_git_hash +set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%" if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY @@ -153,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat del *.obj > NUL 2> NUL -:end_of_build +:end_of_build \ No newline at end of file diff --git a/build_odin.sh b/build_odin.sh index 19bb82a11..0d7e8a26e 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -6,7 +6,6 @@ set -eu : ${LDFLAGS=} : ${LLVM_CONFIG=} -CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\"" CXXFLAGS="$CXXFLAGS -std=c++14" DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" LDFLAGS="$LDFLAGS -pthread -lm" @@ -15,8 +14,12 @@ OS_NAME="$(uname -s)" if [ -d ".git" ] && [ -n "$(command -v git)" ]; then GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD) + GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD) CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\"" +else + GIT_DATE=$(date +"%Y-%m") fi +CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\"" error() { printf "ERROR: %s\n" "$1" diff --git a/core/container/priority_queue/priority_queue.odin b/core/container/priority_queue/priority_queue.odin index 7387a8d09..c62a821f4 100644 --- a/core/container/priority_queue/priority_queue.odin +++ b/core/container/priority_queue/priority_queue.odin @@ -133,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) { n := builtin.len(pq.queue) if 0 <= i && i < n { - if n != i { - pq.swap(pq.queue[:], i, n) - _shift_down(pq, i, n) - _shift_up(pq, i) - } - value, ok = builtin.pop_safe(&pq.queue) + pq.swap(pq.queue[:], i, n-1) + _shift_down(pq, i, n-1) + _shift_up(pq, i) + value, ok = builtin.pop(&pq.queue), true } return } diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 151bd69c3..b9ed1476f 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca return prev } +// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`. +// Example: `json:"key_name,omitempty"` +// This returns the first field as `json_name`, and the rest are returned as `extra`. @(private) json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name = value @@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm defer delete(key, p.allocator) unmarshal_expect_token(p, .Colon) - - field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool { - prev_set := field_used[offset/8] & byte(offset&7) != 0 - field_used[offset/8] |= byte(offset&7) - return prev_set - } field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. @@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm if use_field_idx < 0 { for field, field_idx in fields { - if key == field.name { + tag_value := reflect.struct_tag_get(field.tag, "json") + json_name, _ := json_name_from_tag_value(tag_value) + if json_name == "" && key == field.name { use_field_idx = field_idx break } @@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } } - if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) { + tag_value := reflect.struct_tag_get(field.tag, "json") + json_name, _ := json_name_from_tag_value(tag_value) + if (json_name == "" && field.name == key) || json_name == key { offset = field.offset type = field.type found = true @@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } if field_found { + field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool { + prev_set := field_used[offset/8] & byte(offset&7) != 0 + field_used[offset/8] |= byte(offset&7) + return prev_set + } if field_test(field_used, offset) { return .Multiple_Use_Field } diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 6d93fb879..0fe5c3477 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -2,10 +2,12 @@ #+build !orca package log -import "core:encoding/ansi" +import "base:runtime" import "core:fmt" import "core:strings" import "core:os" +import "core:terminal" +import "core:terminal/ansi" import "core:time" Level_Headers := [?]string{ @@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct { ident: string, } +@(private) global_subtract_stdout_options: Options +@(private) global_subtract_stderr_options: Options + +@(init, private) +init_standard_stream_status :: proc() { + // NOTE(Feoramund): While it is technically possible for these streams to + // be redirected during the runtime of the program, the cost of checking on + // every single log message is not worth it to support such an + // uncommonly-used feature. + if terminal.color_enabled { + // This is done this way because it's possible that only one of these + // streams could be redirected to a file. + if !terminal.is_terminal(os.stdout) { + global_subtract_stdout_options = {.Terminal_Color} + } + if !terminal.is_terminal(os.stderr) { + global_subtract_stderr_options = {.Terminal_Color} + } + } else { + // Override any terminal coloring. + global_subtract_stdout_options = {.Terminal_Color} + global_subtract_stderr_options = {.Terminal_Color} + } +} + create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { data := new(File_Console_Logger_Data, allocator) data.file_handle = h data.ident = ident - return Logger{file_console_logger_proc, data, lowest, opt} + return Logger{file_logger_proc, data, lowest, opt} } destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { @@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg data := new(File_Console_Logger_Data, allocator) data.file_handle = os.INVALID_HANDLE data.ident = ident - return Logger{file_console_logger_proc, data, lowest, opt} + return Logger{console_logger_proc, data, lowest, opt} } destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { free(log.data, allocator) } -file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { - data := cast(^File_Console_Logger_Data)logger_data - h: os.Handle = os.stdout if level <= Level.Error else os.stderr - if data.file_handle != os.INVALID_HANDLE { - h = data.file_handle - } +@(private) +_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) { backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. buf := strings.builder_from_bytes(backing[:]) @@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) } - if data.ident != "" { - fmt.sbprintf(&buf, "[%s] ", data.ident) + if ident != "" { + fmt.sbprintf(&buf, "[%s] ", ident) } //TODO(Hoej): When we have better atomics and such, make this thread-safe fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) } +file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + data := cast(^File_Console_Logger_Data)logger_data + _file_console_logger_proc(data.file_handle, data.ident, level, text, options, location) +} + +console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + options := options + data := cast(^File_Console_Logger_Data)logger_data + h: os.Handle = --- + if level < Level.Error { + h = os.stdout + options -= global_subtract_stdout_options + } else { + h = os.stderr + options -= global_subtract_stderr_options + } + _file_console_logger_proc(h, data.ident, level, text, options, location) +} + do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { RESET :: ansi.CSI + ansi.RESET + ansi.SGR diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 63c7e388f..7f1f4ca87 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast args = make([dynamic]^ast.Expr) for p.curr_tok.kind != .Close_Paren && p.curr_tok.kind != .EOF { - arg := parse_value(p) + arg := parse_value(p) - if p.curr_tok.kind == .Eq { - eq := expect_token(p, .Eq) - if arg != nil { - if _, ok := arg.derived.(^ast.Ident); !ok { - error(p, arg.pos, "expected an identifier for 'key=value'") - } - } - value := parse_value(p) - fv := ast.new(ast.Field_Value, arg.pos, value) - fv.field = arg - fv.sep = eq.pos - fv.value = value + if p.curr_tok.kind == .Eq { + eq := expect_token(p, .Eq) + if arg != nil { + if _, ok := arg.derived.(^ast.Ident); !ok { + error(p, arg.pos, "expected an identifier for 'key=value'") + } + } + value := parse_value(p) + fv := ast.new(ast.Field_Value, arg.pos, value) + fv.field = arg + fv.sep = eq.pos + fv.value = value - arg = fv - } + arg = fv + } - append(&args, arg) + append(&args, arg) allow_token(p, .Comma) or_break - } + } } p.expr_level -= 1 diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index 864532850..cedfbdee1 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator { return heap_allocator() } -temp_allocator_proc :: runtime.arena_allocator_proc - @(private="file") MAX_TEMP_ARENA_COUNT :: 2 - +@(private="file") +MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1 @(private="file", thread_local) global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena -@(private="file", thread_local) -global_default_temp_allocator_index: uint - - -@(require_results) -temp_allocator :: proc() -> runtime.Allocator { - arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index] - if arena.backing_allocator.procedure == nil { - arena.backing_allocator = heap_allocator() - } - - return runtime.Allocator{ - procedure = temp_allocator_proc, - data = arena, - } -} - - - -@(require_results) -temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { - temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc) - return -} - -temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { - runtime.arena_temp_end(temp, loc) -} - @(fini, private) temp_allocator_fini :: proc() { for &arena in global_default_temp_allocator_arenas { @@ -53,18 +23,49 @@ temp_allocator_fini :: proc() { global_default_temp_allocator_arenas = {} } -TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { - runtime.arena_temp_end(temp, loc) - if temp.arena != nil { - global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT - } +Temp_Allocator :: struct { + using arena: ^runtime.Arena, + using allocator: runtime.Allocator, + tmp: runtime.Arena_Temp, + loc: runtime.Source_Code_Location, +} + +TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) { + runtime.arena_temp_end(temp.tmp, temp.loc) } @(deferred_out=TEMP_ALLOCATOR_GUARD_END) -TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { - global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT - tmp := temp_allocator_temp_begin(loc) - return tmp, loc +TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator { + assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!") + good_arena: ^runtime.Arena + for i in 0.. (runtime.Arena_Temp) { + return temp_allocator_begin(tmp.arena) +} +@(private="file") +_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) { + temp_allocator_end(tmp) } @(init, private) diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin index 9e6eaab72..10b06a8ce 100644 --- a/core/os/os2/dir.odin +++ b/core/os/os2/dir.odin @@ -2,6 +2,7 @@ package os2 import "base:runtime" import "core:slice" +import "core:strings" read_dir :: read_directory @@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files size = 100 } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) it := read_directory_iterator_create(f) defer _read_directory_iterator_destroy(&it) - dfi := make([dynamic]File_Info, 0, size, temp_allocator()) + dfi := make([dynamic]File_Info, 0, size, temp_allocator) defer if err != nil { for fi in dfi { file_info_delete(fi, allocator) @@ -194,28 +195,54 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, } // Recursively copies a directory to `dst` from `src` -copy_directory :: proc(dst, src: string, dst_perm := 0o755) -> Error { - switch err := make_directory_all(dst, dst_perm); err { - case nil, .Exist: - // okay - case: +copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { + when #defined(_copy_directory_all_native) { + return _copy_directory_all_native(dst, src, dst_perm) + } else { + return _copy_directory_all(dst, src, dst_perm) + } +} + +@(private) +_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { + err := make_directory(dst, dst_perm) + if err != nil && err != .Exist { return err } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - file_infos := read_all_directory_by_path(src, temp_allocator()) or_return - for fi in file_infos { - TEMP_ALLOCATOR_GUARD() + abs_src := get_absolute_path(src, temp_allocator) or_return + abs_dst := get_absolute_path(dst, temp_allocator) or_return - dst_path := join_path({dst, fi.name}, temp_allocator()) or_return - src_path := fi.fullpath + dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return - if fi.type == .Directory { - copy_directory(dst_path, src_path) or_return + w: Walker + walker_init_path(&w, src) + defer walker_destroy(&w) + + for info in walker_walk(&w) { + _ = walker_error(&w) or_break + + rel := strings.trim_prefix(info.fullpath, abs_src) + + non_zero_resize(&dst_buf, 0) + reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return + append(&dst_buf, abs_dst) + append(&dst_buf, Path_Separator_String) + append(&dst_buf, rel) + + if info.type == .Directory { + err = make_directory(string(dst_buf[:]), dst_perm) + if err != nil && err != .Exist { + return err + } } else { - copy_file(dst_path, src_path) or_return + copy_file(string(dst_buf[:]), info.fullpath) or_return } } + + _ = walker_error(&w) or_return + return nil -} \ No newline at end of file +} diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index a868a02c4..34346c02f 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info it.impl.prev_fi = fi if err != nil { - path, _ := _get_full_path(entry_fd, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path, _ := _get_full_path(entry_fd, temp_allocator) read_directory_iterator_set_error(it, path, err) } diff --git a/core/os/os2/dir_posix_darwin.odin b/core/os/os2/dir_posix_darwin.odin new file mode 100644 index 000000000..3cae50d25 --- /dev/null +++ b/core/os/os2/dir_posix_darwin.odin @@ -0,0 +1,17 @@ +#+private +package os2 + +import "core:sys/darwin" + +_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + csrc := clone_to_cstring(src, temp_allocator) or_return + cdst := clone_to_cstring(dst, temp_allocator) or_return + + if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 { + err = _get_platform_error() + } + + return +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index dc517a9e4..4cf1f8396 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { return } - path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator()) or_else ""}, allocator) or_return + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) defer win32.CloseHandle(handle) @@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct { @(require_results) _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { - TEMP_ALLOCATOR_GUARD() - for !it.impl.no_more_files { err: Error file_info_delete(it.impl.prev_fi, file_allocator()) @@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { wpath = impl.wname[:i] } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - wpath_search := make([]u16, len(wpath)+3, temp_allocator()) + wpath_search := make([]u16, len(wpath)+3, temp_allocator) copy(wpath_search, wpath) wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+1] = '*' diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index 35084893a..13682f76b 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - ckey := strings.clone_to_cstring(key, temp_allocator()) + ckey := strings.clone_to_cstring(key, temp_allocator) cval := posix.getenv(ckey) if cval == nil { return @@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string } _set_env :: proc(key, value: string) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - ckey := strings.clone_to_cstring(key, temp_allocator()) or_return - cval := strings.clone_to_cstring(value, temp_allocator()) or_return + ckey := strings.clone_to_cstring(key, temp_allocator) or_return + cval := strings.clone_to_cstring(value, temp_allocator) or_return if posix.setenv(ckey, cval, true) != nil { err = _get_platform_error_from_errno() @@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) { } _unset_env :: proc(key: string) -> (ok: bool) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - ckey := strings.clone_to_cstring(key, temp_allocator()) + ckey := strings.clone_to_cstring(key, temp_allocator) ok = posix.unsetenv(ckey) == .OK return diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin index 305192c92..faa54e36b 100644 --- a/core/os/os2/env_wasi.odin +++ b/core/os/os2/env_wasi.odin @@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) { g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return defer if err != nil { delete(g_env_buf, file_allocator()) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - envs := make([]cstring, num_envs, temp_allocator()) or_return + envs := make([]cstring, num_envs, temp_allocator) or_return _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) if _err != nil { diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 3ac26a261..6bfde34bb 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string if key == "" { return } - TEMP_ALLOCATOR_GUARD() - wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + wkey, _ := win32_utf8_to_wstring(key, temp_allocator) n := win32.GetEnvironmentVariableW(wkey, nil, 0) if n == 0 { @@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", true } - b := make([]u16, n+1, temp_allocator()) + b := make([]u16, n+1, temp_allocator) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) if n == 0 { @@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string } _set_env :: proc(key, value: string) -> Error { - TEMP_ALLOCATOR_GUARD() - k := win32_utf8_to_wstring(key, temp_allocator()) or_return - v := win32_utf8_to_wstring(value, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + k := win32_utf8_to_wstring(key, temp_allocator) or_return + v := win32_utf8_to_wstring(value, temp_allocator) or_return if !win32.SetEnvironmentVariableW(k, v) { return _get_platform_error() @@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error { } _unset_env :: proc(key: string) -> bool { - TEMP_ALLOCATOR_GUARD() - k, _ := win32_utf8_to_wstring(key, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + k, _ := win32_utf8_to_wstring(key, temp_allocator) return bool(win32.SetEnvironmentVariableW(k, nil)) } _clear_env :: proc() { - TEMP_ALLOCATOR_GUARD() - envs, _ := environ(temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + envs, _ := environ(temp_allocator) for env in envs { for j in 1.. bool { @(require_results) is_file :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - fi, err := stat(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) if err != nil { return false } @@ -303,8 +303,8 @@ is_dir :: is_directory @(require_results) is_directory :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - fi, err := stat(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) if err != nil { return false } @@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool { copy_file :: proc(dst_path, src_path: string) -> Error { + when #defined(_copy_file_native) { + return _copy_file_native(dst_path, src_path) + } else { + return _copy_file(dst_path, src_path) + } +} + +@(private) +_copy_file :: proc(dst_path, src_path: string) -> Error { src := open(src_path) or_return defer close(src) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 811ee7055..a1ead7f9f 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -66,8 +66,8 @@ _standard_stream_init :: proc() { } _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return // Just default to using O_NOCTTY because needing to open a controlling // terminal would be incredibly rare. This has no effect on files while @@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { linux.close(fd) @@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - old_name_cstr := temp_cstring(old_name) or_return - new_name_cstr := temp_cstring(new_name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - old_name_cstr := temp_cstring(old_name) or_return - new_name_cstr := temp_cstring(new_name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) } _symlink :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - old_name_cstr := temp_cstring(old_name) or_return - new_name_cstr := temp_cstring(new_name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) } @@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st } _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _read_link_cstr(name_cstr, allocator) } _chdir :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.chdir(name_cstr)) } @@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error { } _chmod :: proc(name: string, mode: int) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) } @@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error { // NOTE: will throw error without super user priviledges _chown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } // NOTE: will throw error without super user priviledges _lchown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } @@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return times := [2]linux.Time_Spec { { uint(atime._nsec) / uint(time.Second), @@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(name: string) -> bool { - TEMP_ALLOCATOR_GUARD() - name_cstr, _ := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr, _ := clone_to_cstring(name, temp_allocator) return linux.access(name_cstr, linux.F_OK) == .NONE } @@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool { _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := clone_to_cstring(name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _read_entire_pseudo_file_cstring(name_cstr, allocator) } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 43d5866b1..2d74618ee 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err if .Trunc in flags { sys_flags += {.TRUNC} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) if fd < 0 { @@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error { return nil } -_remove :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_remove :: proc(name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.remove(cname) != 0 { return _get_platform_error() } return nil } -_rename :: proc(old_path, new_path: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cold := temp_cstring(old_path) - cnew := temp_cstring(new_path) +_rename :: proc(old_path, new_path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_path, temp_allocator) or_return + cnew := clone_to_cstring(new_path, temp_allocator) or_return if posix.rename(cold, cnew) != 0 { return _get_platform_error() } return nil } -_link :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cold := temp_cstring(old_name) - cnew := temp_cstring(new_name) +_link :: proc(old_name, new_name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_name, temp_allocator) or_return + cnew := clone_to_cstring(new_name, temp_allocator) or_return if posix.link(cold, cnew) != .OK { return _get_platform_error() } return nil } -_symlink :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cold := temp_cstring(old_name) - cnew := temp_cstring(new_name) +_symlink :: proc(old_name, new_name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_name, temp_allocator) or_return + cnew := clone_to_cstring(new_name, temp_allocator) or_return if posix.symlink(cold, cnew) != .OK { return _get_platform_error() } @@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error { } _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return buf: [dynamic]byte buf.allocator = allocator @@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er } } -_chdir :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_chdir :: proc(name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.chdir(cname) != .OK { return _get_platform_error() } @@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error { return nil } -_chmod :: proc(name: string, mode: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_chmod :: proc(name: string, mode: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { return _get_platform_error() } @@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { return nil } -_chown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_chown :: proc(name: string, uid, gid: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { return _get_platform_error() } @@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error { } _lchown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { return _get_platform_error() } return nil } -_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { +_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) { times := [2]posix.timeval{ { tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ @@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { }, } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.utimes(cname, ×) != .OK { return _get_platform_error() @@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - cpath := temp_cstring(path) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cpath, err := clone_to_cstring(path, temp_allocator) + if err != nil { return false } return posix.access(cpath) == .OK } diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin index 920a63a71..aed3e56f5 100644 --- a/core/os/os2/file_posix_darwin.odin +++ b/core/os/os2/file_posix_darwin.odin @@ -3,6 +3,7 @@ package os2 import "base:runtime" +import "core:sys/darwin" import "core:sys/posix" _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { @@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo return clone_to_cstring(string(cstring(&buf[0])), allocator) } + +_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + csrc := clone_to_cstring(src_path, temp_allocator) or_return + cdst := clone_to_cstring(dst_path, temp_allocator) or_return + + // Disallow directories, as specified by the generic implementation. + + stat: posix.stat_t + if posix.stat(csrc, &stat) != .OK { + err = _get_platform_error() + return + } + + if posix.S_ISDIR(stat.st_mode) { + err = .Invalid_File + return + } + + ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL) + if ret < 0 { + err = _get_platform_error() + } + + return +} diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin index 74b6374ec..d2946098b 100644 --- a/core/os/os2/file_posix_other.odin +++ b/core/os/os2/file_posix_other.odin @@ -7,8 +7,8 @@ import "base:runtime" import "core:sys/posix" _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) buf: [posix.PATH_MAX]byte path = posix.realpath(cname, raw_data(buf[:])) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 94e51a14c..40d012183 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -12,30 +12,7 @@ import win32 "core:sys/windows" INVALID_HANDLE :: ~uintptr(0) -// NOTE(Jeroen): We don't translate mode flags for Linux when given to `chmod`. -// Let's not do so for Windows for `chmod` or `read_directory_iterator` either. -// They're *not* portable between Windows and non-Windows platforms. -// -// It also leads to information loss as flags like Archive, Hidden and System have no equivalent there. -// We can of course parse them so we can set the `.Symlink` and `.Directory` type, but we shouldn't pretend -// that 0o644 is meaningful when returned as a mode. -// `C:\bootmgr` as an example has attributes read only, hidden, system, archive. In no way is it sensible to replace that with 0o444. -FILE_ATTRIBUTE_READONLY :: win32.FILE_ATTRIBUTE_READONLY // 0x00000001 -FILE_ATTRIBUTE_HIDDEN :: win32.FILE_ATTRIBUTE_HIDDEN // 0x00000002 -FILE_ATTRIBUTE_SYSTEM :: win32.FILE_ATTRIBUTE_SYSTEM // 0x00000004 -FILE_ATTRIBUTE_DIRECTORY :: win32.FILE_ATTRIBUTE_DIRECTORY // 0x00000010 -FILE_ATTRIBUTE_ARCHIVE :: win32.FILE_ATTRIBUTE_ARCHIVE // 0x00000020 -FILE_ATTRIBUTE_DEVICE :: win32.FILE_ATTRIBUTE_DEVICE // 0x00000040 -FILE_ATTRIBUTE_NORMAL :: win32.FILE_ATTRIBUTE_NORMAL // 0x00000080 -FILE_ATTRIBUTE_TEMPORARY :: win32.FILE_ATTRIBUTE_TEMPORARY // 0x00000100 -FILE_ATTRIBUTE_SPARSE_FILE :: win32.FILE_ATTRIBUTE_SPARSE_FILE // 0x00000200 -FILE_ATTRIBUTE_REPARSE_Point :: win32.FILE_ATTRIBUTE_REPARSE_Point // 0x00000400 -FILE_ATTRIBUTE_REPARSE_POINT :: win32.FILE_ATTRIBUTE_REPARSE_POINT // 0x00000400 -FILE_ATTRIBUTE_COMPRESSED :: win32.FILE_ATTRIBUTE_COMPRESSED // 0x00000800 -FILE_ATTRIBUTE_OFFLINE :: win32.FILE_ATTRIBUTE_OFFLINE // 0x00001000 -FILE_ATTRIBUTE_NOT_CONTENT_INDEXED :: win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED // 0x00002000 -FILE_ATTRIBUTE_ENCRYPTED :: win32.FILE_ATTRIBUTE_ENCRYPTED // 0x00004000 - +S_IWRITE :: 0o200 _ERROR_BAD_NETPATH :: 53 MAX_RW :: 1<<30 @@ -109,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u err = .Not_Exist return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - path := _fix_long_path(name, temp_allocator()) or_return + path := _fix_long_path(name, temp_allocator) or_return access: u32 switch flags & {.Read, .Write} { case {.Read}: access = win32.FILE_GENERIC_READ @@ -145,7 +122,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u } attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS - if u32(perm) & FILE_ATTRIBUTE_NORMAL == 0 { + if perm & S_IWRITE == 0 { attrs = win32.FILE_ATTRIBUTE_READONLY if create_mode == win32.CREATE_ALWAYS { // NOTE(bill): Open has just asked to create a file in read-only mode. @@ -580,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - p := _fix_long_path(name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + p := _fix_long_path(name, temp_allocator) or_return err, err1: Error if !win32.DeleteFileW(p) { err = _get_platform_error() @@ -618,9 +595,9 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - TEMP_ALLOCATOR_GUARD() - from := _fix_long_path(old_path, temp_allocator()) or_return - to := _fix_long_path(new_path, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + from := _fix_long_path(old_path, temp_allocator) or_return + to := _fix_long_path(new_path, temp_allocator) or_return if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { return nil } @@ -629,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error { } _link :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - o := _fix_long_path(old_name, temp_allocator()) or_return - n := _fix_long_path(new_name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + o := _fix_long_path(old_name, temp_allocator) or_return + n := _fix_long_path(new_name, temp_allocator) or_return if win32.CreateHardLinkW(n, o, nil) { return nil } @@ -692,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([]u16, n+1, temp_allocator()) + buf := make([]u16, n+1, temp_allocator) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) if n == 0 { return "", _get_platform_error() @@ -718,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - p := _fix_long_path(name, temp_allocator()) or_return + p := _fix_long_path(name, temp_allocator) or_return handle := _open_sym_link(p) or_return defer win32.CloseHandle(handle) @@ -771,10 +748,20 @@ _fchmod :: proc(f: ^File, mode: int) -> Error { if f == nil || f.impl == nil { return nil } + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(_handle(f), &d) { + return _get_platform_error() + } + attrs := d.dwFileAttributes + if mode & S_IWRITE != 0 { + attrs &~= win32.FILE_ATTRIBUTE_READONLY + } else { + attrs |= win32.FILE_ATTRIBUTE_READONLY + } info: win32.FILE_BASIC_INFO - info.FileAttributes = win32.DWORD(mode) - if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) { + info.FileAttributes = attrs + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { return _get_platform_error() } return nil @@ -785,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chdir :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - p := _fix_long_path(name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + p := _fix_long_path(name, temp_allocator) or_return if !win32.SetCurrentDirectoryW(p) { return _get_platform_error() } @@ -834,8 +821,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - wpath, _ := _fix_long_path(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + wpath, _ := _fix_long_path(path, temp_allocator) attribs := win32.GetFileAttributesW(wpath) return attribs != win32.INVALID_FILE_ATTRIBUTES } diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index ce253d17b..9616af8b0 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri return cstring(&buf[0]), nil } -@(require_results) -temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error { - return clone_to_cstring(s, temp_allocator()) -} - @(require_results) string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { s := string(b) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 47ac0236d..e12aa3c9c 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri return strings.clone(".", allocator) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) // The extra byte is to simplify appending path elements by letting the // loop to end each with a separator. We'll trim the last one when we're done. - buffer := make([]u8, len(path) + 1, temp_allocator()) or_return + buffer := make([]u8, len(path) + 1, temp_allocator) or_return // This is the only point where Windows and POSIX differ, as Windows has // alphabet-based volumes for root paths. @@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { for e, i in elems { if e != "" { - TEMP_ALLOCATOR_GUARD() - p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return return clean_path(p, allocator) } } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 410b4cb28..8b185f419 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(path: string, perm: int) -> Error { - TEMP_ALLOCATOR_GUARD() - path_cstr := temp_cstring(path) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path_cstr := clone_to_cstring(path, temp_allocator) or_return return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) } @@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { } return _get_platform_error(errno) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) // need something we can edit, and use to generate cstrings - path_bytes := make([]u8, len(path) + 1, temp_allocator()) + path_bytes := make([]u8, len(path) + 1, temp_allocator) // zero terminate the byte slice to make it a valid cstring copy(path_bytes, path) @@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error { return nil } - TEMP_ALLOCATOR_GUARD() - path_cstr := temp_cstring(path) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path_cstr := clone_to_cstring(path, temp_allocator) or_return fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) #partial switch errno { @@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) } _set_working_directory :: proc(dir: string) -> Error { - dir_cstr := temp_cstring(dir) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + dir_cstr := clone_to_cstring(dir, temp_allocator) or_return return _get_platform_error(linux.chdir(dir_cstr)) } _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([dynamic]byte, 1024, temp_allocator()) or_return + buf := make([dynamic]byte, 1024, temp_allocator) or_return for { n, errno := linux.readlink("/proc/self/exe", buf[:]) if errno != .NONE { @@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: } return } + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {}) + if errno != nil { + err = _get_platform_error(errno) + return + } + defer linux.close(fd) + + return _get_full_path(fd, allocator) +} diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin index f56a91fd6..815102dea 100644 --- a/core/os/os2/path_netbsd.odin +++ b/core/os/os2/path_netbsd.odin @@ -5,9 +5,9 @@ import "base:runtime" import "core:sys/posix" _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([dynamic]byte, 1024, temp_allocator()) or_return + buf := make([dynamic]byte, 1024, temp_allocator) or_return for { n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) if n < 0 { diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin index 37b5de927..cbc0346d4 100644 --- a/core/os/os2/path_openbsd.odin +++ b/core/os/os2/path_openbsd.odin @@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err return real(arg, allocator) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := strings.builder_make(temp_allocator()) + buf := strings.builder_make(temp_allocator) - paths := get_env("PATH", temp_allocator()) + paths := get_env("PATH", temp_allocator) for dir in strings.split_iterator(&paths, ":") { strings.builder_reset(&buf) strings.write_string(&buf, dir) diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 39bd0a188..f22cd446b 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool { return c == _Path_Separator } -_mkdir :: proc(name: string, perm: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_mkdir :: proc(name: string, perm: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { return _get_platform_error() } @@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return .Invalid_Path } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) if exists(path) { return .Exist } - clean_path := clean_path(path, temp_allocator()) or_return + clean_path := clean_path(path, temp_allocator) or_return return internal_mkdir_all(clean_path, perm) internal_mkdir_all :: proc(path: string, perm: int) -> Error { @@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { } } -_remove_all :: proc(path: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cpath := temp_cstring(path) +_remove_all :: proc(path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cpath := clone_to_cstring(path, temp_allocator) or_return dir := posix.opendir(cpath) if dir == nil { @@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error { continue } - fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) + fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator) if entry.d_type == .DIR { _remove_all(fullpath[:len(fullpath)-1]) or_return } else { @@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error { } _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) buf: [dynamic]byte - buf.allocator = temp_allocator() + buf.allocator = temp_allocator size := uint(posix.PATH_MAX) cwd: cstring @@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er } _set_working_directory :: proc(dir: string) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() - cdir := temp_cstring(dir) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cdir := clone_to_cstring(dir, temp_allocator) or_return if posix.chdir(cdir) != .OK { err = _get_platform_error() } return } + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_cstr := clone_to_cstring(rel, temp_allocator) or_return + path_ptr := posix.realpath(rel_cstr, nil) + if path_ptr == nil { + return "", Platform_Error(posix.errno()) + } + defer posix.free(path_ptr) + + path_str := clone_string(string(path_ptr), allocator) or_return + return path_str, nil +} diff --git a/core/os/os2/path_posixfs.odin b/core/os/os2/path_posixfs.odin index 8f9d43d63..0736e73d1 100644 --- a/core/os/os2/path_posixfs.odin +++ b/core/os/os2/path_posixfs.odin @@ -4,10 +4,6 @@ package os2 // This implementation is for all systems that have POSIX-compliant filesystem paths. -import "base:runtime" -import "core:strings" -import "core:sys/posix" - _are_paths_identical :: proc(a, b: string) -> (identical: bool) { return a == b } @@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool { return len(path) > 0 && _is_path_separator(path[0]) } -_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - rel := path - if rel == "" { - rel = "." - } - TEMP_ALLOCATOR_GUARD() - rel_cstr := strings.clone_to_cstring(rel, temp_allocator()) - path_ptr := posix.realpath(rel_cstr, nil) - if path_ptr == nil { - return "", Platform_Error(posix.errno()) - } - defer posix.free(path_ptr) - - path_str := strings.clone(string(path_ptr), allocator) - return path_str, nil -} - _get_relative_path_handle_start :: proc(base, target: string) -> bool { base_rooted := len(base) > 0 && _is_path_separator(base[0]) target_rooted := len(target) > 0 && _is_path_separator(target[0]) diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin index 7aee8fcc0..b8240e188 100644 --- a/core/os/os2/path_wasi.odin +++ b/core/os/os2/path_wasi.odin @@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return .Invalid_Path } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) if exists(path) { return .Exist } - clean_path := clean_path(path, temp_allocator()) + clean_path := clean_path(path, temp_allocator) return internal_mkdir_all(clean_path) internal_mkdir_all :: proc(path: string) -> Error { diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index dd9b7748c..c2e51040f 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(name: string, perm: int) -> Error { - TEMP_ALLOCATOR_GUARD() - if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) { return _get_platform_error() } return nil @@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return p, false, nil } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - dir_stat, err := stat(path, temp_allocator()) + dir_stat, err := stat(path, temp_allocator) if err == nil { if dir_stat.type == .Directory { return nil @@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { err = mkdir(path, perm) if err != nil { - new_dir_stat, err1 := lstat(path, temp_allocator()) + new_dir_stat, err1 := lstat(path, temp_allocator) if err1 == nil && new_dir_stat.type == .Directory { return nil } @@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error { return nil } - TEMP_ALLOCATOR_GUARD() - dir := win32_utf8_to_wstring(path, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + dir := win32_utf8_to_wstring(path, temp_allocator) or_return empty: [1]u16 @@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { win32.AcquireSRWLockExclusive(&cwd_lock) - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return + dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. @@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er } _set_working_directory :: proc(dir: string) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() - wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return win32.AcquireSRWLockExclusive(&cwd_lock) @@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) { } _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([dynamic]u16, 512, temp_allocator()) or_return + buf := make([dynamic]u16, 512, temp_allocator) or_return for { ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) if ret == 0 { @@ -187,7 +187,6 @@ init_long_path_support :: proc() { if value == 1 { can_use_long_paths = true } - } @(require_results) @@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string { return path } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) PREFIX :: `\\?` - path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator()) + path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator) copy(path_buf, PREFIX) n := len(path) r, w := 0, len(PREFIX) @@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s start += 1 } copy(buffer, path[:start]) + for n in 0.. (absol if rel == "" { rel = "." } - TEMP_ALLOCATOR_GUARD() - rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator) n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil) if n == 0 { return "", Platform_Error(win32.GetLastError()) } - buf := make([]u16, n, temp_allocator()) or_return + buf := make([]u16, n, temp_allocator) or_return n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil) if n == 0 { return "", Platform_Error(win32.GetLastError()) diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index afb398c8d..170f0ea1a 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -50,7 +50,7 @@ _get_ppid :: proc() -> int { @(private="package") _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) #partial switch errno { @@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) } defer linux.close(dir_fd) - dynamic_list := make([dynamic]int, temp_allocator()) or_return + dynamic_list := make([dynamic]int, temp_allocator) or_return - buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return + buf := make([dynamic]u8, 128, 128, temp_allocator) or_return loop: for { buflen: int buflen, errno = linux.getdents(dir_fd, buf[:]) @@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) @(private="package") _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) info.pid = pid @@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator passwd_bytes: []u8 passwd_err: Error - passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) + passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator) if passwd_err != nil { err = passwd_err break username_if @@ -168,7 +168,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/cmdline") - cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) + cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) if cmdline_err != nil || len(cmdline_bytes) == 0 { err = cmdline_err break cmdline_if @@ -189,7 +189,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/cwd") - cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail + cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail if cwd_err == nil && .Working_Dir in selection { info.working_dir = strings.clone(cwd, allocator) or_return info.fields += {.Working_Dir} @@ -245,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/stat") - proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) + proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) if stat_err != nil { err = stat_err break stat_if @@ -284,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator Nice, //... etc, } - stat_fields := strings.split(stats, " ", temp_allocator()) or_return + stat_fields := strings.split(stats, " ", temp_allocator) or_return if len(stat_fields) <= int(Fields.Nice) { break stat_if @@ -327,7 +327,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/exe") - if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator()); exe_err == nil { + if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil { info.executable_path = strings.clone(string(exe_bytes), allocator) or_return info.fields += {.Executable_Path} } else { @@ -341,7 +341,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/environ") - if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil { + if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil { env := string(env_bytes) env_list := make([dynamic]string, allocator) or_return @@ -392,7 +392,7 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err @(private="package") _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) if len(desc.command) == 0 { return process, .Invalid_Command @@ -401,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { dir_fd := linux.AT_FDCWD errno: linux.Errno if desc.working_dir != "" { - dir_cstr := temp_cstring(desc.working_dir) or_return + dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { return process, _get_platform_error(errno) } @@ -414,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { exe_path: cstring executable_name := desc.command[0] if strings.index_byte(executable_name, '/') < 0 { - path_env := get_env("PATH", temp_allocator()) - path_dirs := split_path_list(path_env, temp_allocator()) or_return + path_env := get_env("PATH", temp_allocator) + path_dirs := split_path_list(path_env, temp_allocator) or_return - exe_builder := strings.builder_make(temp_allocator()) or_return + exe_builder := strings.builder_make(temp_allocator) or_return found: bool for dir in path_dirs { @@ -444,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } } } else { - exe_path = temp_cstring(executable_name) or_return + exe_path = clone_to_cstring(executable_name, temp_allocator) or_return if linux.access(exe_path, linux.X_OK) != .NONE { return process, .Not_Exist } @@ -452,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { // args and environment need to be a list of cstrings // that are terminated by a nil pointer. - cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return + cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return for command, i in desc.command { - cargs[i] = temp_cstring(command) or_return + cargs[i] = clone_to_cstring(command, temp_allocator) or_return } // Use current process' environment if description didn't provide it. env: [^]cstring if desc.env == nil { // take this process's current environment - env = raw_data(export_cstring_environment(temp_allocator())) + env = raw_data(export_cstring_environment(temp_allocator)) } else { - cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return + cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return for env, i in desc.env { - cenv[i] = temp_cstring(env) or_return + cenv[i] = clone_to_cstring(env, temp_allocator) or_return } env = &cenv[0] } @@ -593,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) stat_path_buf: [48]u8 path_builder := strings.builder_from_bytes(stat_path_buf[:]) @@ -602,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { strings.write_string(&path_builder, "/stat") stat_buf: []u8 - stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) + stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) if err != nil { return } diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin index 6070b19d6..fcacdf654 100644 --- a/core/os/os2/process_posix.odin +++ b/core/os/os2/process_posix.odin @@ -52,14 +52,14 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) // search PATH if just a plain name is provided. - exe_builder := strings.builder_make(temp_allocator()) + exe_builder := strings.builder_make(temp_allocator) exe_name := desc.command[0] if strings.index_byte(exe_name, '/') < 0 { - path_env := get_env("PATH", temp_allocator()) - path_dirs := split_path_list(path_env, temp_allocator()) or_return + path_env := get_env("PATH", temp_allocator) + path_dirs := split_path_list(path_env, temp_allocator) or_return found: bool for dir in path_dirs { @@ -108,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } cwd: cstring; if desc.working_dir != "" { - cwd = temp_cstring(desc.working_dir) + cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return } - cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) + cmd := make([]cstring, len(desc.command) + 1, temp_allocator) for part, i in desc.command { - cmd[i] = temp_cstring(part) + cmd[i] = clone_to_cstring(part, temp_allocator) or_return } env: [^]cstring @@ -121,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { // take this process's current environment env = posix.environ } else { - cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) + cenv := make([]cstring, len(desc.env) + 1, temp_allocator) for env, i in desc.env { - cenv[i] = temp_cstring(env) + cenv[i] = clone_to_cstring(env, temp_allocator) or_return } env = raw_data(cenv) } diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 0ea1f643c..ac2d4b78c 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) info.pid = pid // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), @@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator break args } - buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) + buf := runtime.make_aligned([]byte, length, 4, temp_allocator) if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { if err == nil { err = _get_platform_error() @@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buffer := make([]i32, ret, temp_allocator()) + buffer := make([]i32, ret, temp_allocator) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) if ret < 0 { err = _get_platform_error() diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 69764dff7..199e5ad74 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator if err != nil { break read_peb } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD() - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) if err != nil { break read_peb @@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } if .Environment in selection { - TEMP_ALLOCATOR_GUARD() + temp_allocator_scope(temp_allocator) env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator()) or_return + envs_w := make([]u16, env_len, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) if err != nil { break read_peb @@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields += {.Environment} } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD() - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) if err != nil { break read_peb @@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields if err != nil { break read_peb } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD() - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) if err != nil { break read_peb @@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields } } if .Environment in selection { - TEMP_ALLOCATOR_GUARD() + temp_allocator_scope(temp_allocator) env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator()) or_return + envs_w := make([]u16, env_len, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) if err != nil { break read_peb @@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.fields += {.Environment} } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD() - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) if err != nil { break read_peb @@ -419,15 +421,15 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, @(private="package") _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - TEMP_ALLOCATOR_GUARD() - command_line := _build_command_line(desc.command, temp_allocator()) - command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + command_line := _build_command_line(desc.command, temp_allocator) + command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return environment := desc.env if desc.env == nil { - environment = environ(temp_allocator()) or_return + environment = environ(temp_allocator) or_return } - environment_block := _build_environment_block(environment, temp_allocator()) - environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return + environment_block := _build_environment_block(environment, temp_allocator) + environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return stderr_handle: win32.HANDLE stdout_handle: win32.HANDLE @@ -474,7 +476,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) } - working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil + working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil process_info: win32.PROCESS_INFORMATION ok := win32.CreateProcessW( nil, @@ -612,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path } _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) token_handle: win32.HANDLE if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { err = _get_platform_error() @@ -627,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc } err = nil } - token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return)) if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { err = _get_platform_error() return @@ -643,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc err = _get_platform_error() return } - username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return - domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return + domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return return strings.concatenate({domain, "\\", username}, allocator) } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 7d76902eb..d6b524684 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path @(require_results) modification_time :: proc(f: ^File) -> (time.Time, Error) { - TEMP_ALLOCATOR_GUARD() - fi, err := fstat(f, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := fstat(f, temp_allocator) return fi.modification_time, err } @(require_results) modification_time_by_path :: proc(path: string) -> (time.Time, Error) { - TEMP_ALLOCATOR_GUARD() - fi, err := stat(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) return fi.modification_time, err } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 7bff08f29..373765be5 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return fd, errno := linux.open(name_cstr, {}) if errno != .NONE { @@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err } _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) if errno != .NONE { diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index 260dc7b52..6ffbdf1da 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err return } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return fd := posix.open(cname, {}) if fd == -1 { @@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. // NOTE: This might not be correct when given "/symlink/foo.txt", // you would want that to resolve "/symlink", but not resolve "foo.txt". - fullpath := clean_path(name, temp_allocator()) or_return + fullpath := clean_path(name, temp_allocator) or_return assert(len(fullpath) > 0) switch { case fullpath[0] == '/': // nothing. case fullpath == ".": - fullpath = getwd(temp_allocator()) or_return + fullpath = getwd(temp_allocator) or_return case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': fullpath = fullpath[2:] fallthrough case: fullpath = concatenate({ - getwd(temp_allocator()) or_return, + getwd(temp_allocator) or_return, "/", fullpath, - }, temp_allocator()) or_return + }, temp_allocator) or_return } stat: posix.stat_t - if posix.lstat(temp_cstring(fullpath), &stat) != .OK { + c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return + if posix.lstat(c_fullpath, &stat) != .OK { err = _get_platform_error() return } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 8c3d4a610..3cdc80405 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path name = "." } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - p := win32_utf8_to_utf16(name, temp_allocator()) or_return + p := win32_utf8_to_utf16(name, temp_allocator) or_return n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { return "", _get_platform_error() } - buf := make([]u16, n+1, temp_allocator()) + buf := make([]u16, n+1, temp_allocator) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { return "", _get_platform_error() @@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt if len(name) == 0 { return {}, .Not_Exist } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - wname := _fix_long_path(name, temp_allocator()) or_return + wname := _fix_long_path(name, temp_allocator) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { @@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([]u16, max(n, 260)+1, temp_allocator()) + buf := make([]u16, max(n, 260)+1, temp_allocator) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_from_buf(buf[:n], allocator) } @@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { return nil, _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - buf := make([]u16, max(n, 260)+1, temp_allocator()) + buf := make([]u16, max(n, 260)+1, temp_allocator) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_strip_prefix(buf[:n]), nil } @@ -212,15 +212,11 @@ _file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes } _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) { - // NOTE(Jeroen): We don't translate mode flags for Linux when given to `chmod`. - // Let's not do so for Windows for `chmod` or `read_directory_iterator` either. - // They're *not* portable between Windows and non-Windows platforms. - // - // It also leads to information loss as flags like Archive, Hidden and System have no equivalent there. - // We can of course parse them so we can set the `.Symlink` and `.Directory` type, but we shouldn't pretend - // that 0o644 is meaningful when returned as a mode. - // `C:\bootmgr` as an example has attributes read only, hidden, system, archive. In no way is it sensible to replace that with 0o444. - mode = int(file_attributes) + if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + mode |= 0o444 + } else { + mode |= 0o666 + } is_sym := false if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { @@ -233,6 +229,7 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi type = .Symlink } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { type = .Directory + mode |= 0o111 } else if h != nil { type = file_type(h) } @@ -329,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool { return false } -_is_UNC :: proc(path: string) -> bool { - return _volume_name_len(path) > 2 -} - -_volume_name_len :: proc(path: string) -> int { +_volume_name_len :: proc(path: string) -> (length: int) { if len(path) < 2 { return 0 } - c := path[0] + if path[1] == ':' { - switch c { + switch path[0] { case 'a'..='z', 'A'..='Z': return 2 } } - // URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && - !_is_path_separator(path[2]) && path[2] != '.' { - for n := 3; n < l-1; n += 1 { - if _is_path_separator(path[n]) { - n += 1 - if !_is_path_separator(path[n]) { - if path[n] == '.' { - break - } - } - for ; n < l; n += 1 { - if _is_path_separator(path[n]) { - break - } - } - return n + /* + See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + Further allowed paths can be of the form of: + - \\server\share or \\server\share\more\path + - \\?\C:\... + - \\.\PhysicalDriveX + */ + // Any remaining kind of path has to start with two slashes. + if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) { + return 0 + } + + // Device path. The volume name is the whole string + if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) { + return len(path) + } + + // We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share` + prefix := 2 + + // File namespace. + if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) { + if _is_path_separator(path[4]) { + // `\\?\\` UNC path in file namespace + prefix = 5 + } + + if len(path) >= 6 && path[5] == ':' { + switch path[4] { + case 'a'..='z', 'A'..='Z': + return 6 + case: + return 0 } - break } } - return 0 -} + + // UNC path, minimum version of the volume is `\\h\s` for host, share. + // Can also contain an IP address in the host position. + slash_count := 0 + for i in prefix.. 0 { + slash_count += 1 + + if slash_count == 2 { + return i + } + } + } + + return len(path) +} \ No newline at end of file diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index e93117f02..ad20b5706 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? // The caller must `close` the file once finished with. @(require_results) create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { - TEMP_ALLOCATOR_GUARD() - dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return prefix = temp_join_path(dir, prefix) or_return rand_buf: [10]byte - name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) attempts := 0 for { @@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp // If `dir` is an empty tring, `temp_directory()` will be used. @(require_results) make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() - dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return prefix = temp_join_path(dir, prefix) or_return rand_buf: [10]byte - name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) attempts := 0 for { @@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) return "", err } if err == .Not_Exist { - if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist { + if _, serr := stat(dir, temp_allocator); serr == .Not_Exist { return "", serr } } @@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { @(private="file") temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { - return concatenate({dir, name}, temp_allocator(),) + return concatenate({dir, name}, temp_allocator,) } - return concatenate({dir, Path_Separator_String, name}, temp_allocator()) + return concatenate({dir, Path_Separator_String, name}, temp_allocator) } diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index 4eacbc54a..310720cbe 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -4,8 +4,8 @@ package os2 import "base:runtime" _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - TEMP_ALLOCATOR_GUARD() - tmpdir := get_env("TMPDIR", temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + tmpdir := get_env("TMPDIR", temp_allocator) if tmpdir == "" { tmpdir = "/tmp" } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 3e3e1285c..9d75ef99d 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er if n == 0 { return "", nil } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) + b := make([]u16, max(win32.MAX_PATH, n), temp_allocator) n = win32.GetTempPathW(u32(len(b)), raw_data(b)) if n == 3 && b[1] == ':' && b[2] == '\\' { diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 7fcc87bf8..b2856a319 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -4,27 +4,27 @@ import "base:runtime" @(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) #partial switch ODIN_OS { case .Windows: - dir = get_env("LocalAppData", temp_allocator()) + dir = get_env("LocalAppData", temp_allocator) if dir != "" { - dir = clone_string(dir, allocator) or_return + dir = clone_string(dir, temp_allocator) or_return } case .Darwin: - dir = get_env("HOME", temp_allocator()) + dir = get_env("HOME", temp_allocator) if dir != "" { - dir = concatenate({dir, "/Library/Caches"}, allocator) or_return + dir = concatenate({dir, "/Library/Caches"}, temp_allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) if dir == "" { - dir = get_env("HOME", temp_allocator()) + dir = get_env("HOME", temp_allocator) if dir == "" { return } - dir = concatenate({dir, "/.cache"}, allocator) or_return + dir = concatenate({dir, "/.cache"}, temp_allocator) or_return } } if dir == "" { @@ -35,23 +35,23 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error @(require_results) user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) #partial switch ODIN_OS { case .Windows: - dir = get_env("AppData", temp_allocator()) + dir = get_env("AppData", temp_allocator) if dir != "" { dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", temp_allocator()) + dir = get_env("HOME", temp_allocator) if dir != "" { dir = concatenate({dir, "/.config"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CONFIG_HOME", allocator) if dir == "" { - dir = get_env("HOME", temp_allocator()) + dir = get_env("HOME", temp_allocator) if dir == "" { return } diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 26a737bd1..4cecd1911 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1095,6 +1095,39 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { return transmute(f64)bits, ok } + if len(str) > 2 && str[0] == '0' && str[1] == 'h' { + nr = 2 + + as_int: u64 + digits: int + for r in str[2:] { + if r == '_' { + nr += 1 + continue + } + v := u64(_digit_value(r)) + if v >= 16 { + break + } + as_int *= 16 + as_int += v + digits += 1 + } + nr += digits + ok = len(str) == nr + + switch digits { + case 4: + value = cast(f64)transmute(f16)cast(u16)as_int + case 8: + value = cast(f64)transmute(f32)cast(u32)as_int + case 16: + value = transmute(f64)as_int + case: + ok = false + } + return + } if value, nr, ok = check_special(str); ok { return diff --git a/core/strings/builder.odin b/core/strings/builder.odin index e5a88527a..05382f04e 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -311,7 +311,7 @@ Returns: - res: A cstring of the Builder's buffer upon success - err: An optional allocator error if one occured, `nil` otherwise */ -to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) { +to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error { n := append(&b.buf, 0) or_return if n != 1 { return nil, .Out_Of_Memory diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index 254da75ad..7b14d8ebc 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -99,7 +99,7 @@ Application_setTitle :: proc "c" (self: ^Application, title: ^String) { } @(objc_type=Application, objc_name="mainMenu") -Window_mainMenu :: proc "c" (self: ^Application) -> ^Menu { +Application_mainMenu :: proc "c" (self: ^Application) -> ^Menu { return msgSend(^Menu, self, "mainMenu") } diff --git a/core/sys/darwin/Foundation/NSArray.odin b/core/sys/darwin/Foundation/NSArray.odin index b238f63f8..0977c6469 100644 --- a/core/sys/darwin/Foundation/NSArray.odin +++ b/core/sys/darwin/Foundation/NSArray.odin @@ -40,3 +40,49 @@ Array_objectAs :: proc "c" (self: ^Array, index: UInteger, $T: typeid) -> T wher Array_count :: proc "c" (self: ^Array) -> UInteger { return msgSend(UInteger, self, "count") } + + +@(objc_class="NSMutableArray") +MutableArray :: struct { + using _: Copying(MutableArray), +} + +@(objc_type=MutableArray, objc_name="alloc", objc_is_class_method=true) +MutableArray_alloc :: proc "c" () -> ^MutableArray { + return msgSend(^MutableArray, MutableArray, "alloc") +} + +@(objc_type=MutableArray, objc_name="init") +MutableArray_init :: proc "c" (self: ^MutableArray) -> ^MutableArray { + return msgSend(^MutableArray, self, "init") +} + +@(objc_type=MutableArray, objc_name="initWithObjects") +MutableArray_initWithObjects :: proc "c" (self: ^MutableArray, objects: [^]^Object, count: UInteger) -> ^MutableArray { + return msgSend(^MutableArray, self, "initWithObjects:count:", objects, count) +} + +@(objc_type=MutableArray, objc_name="initWithCoder") +MutableArray_initWithCoder :: proc "c" (self: ^MutableArray, coder: ^Coder) -> ^MutableArray { + return msgSend(^MutableArray, self, "initWithCoder:", coder) +} + +@(objc_type=MutableArray, objc_name="object") +MutableArray_object :: proc "c" (self: ^MutableArray, index: UInteger) -> ^Object { + return msgSend(^Object, self, "objectAtIndex:", index) +} +@(objc_type=MutableArray, objc_name="objectAs") +MutableArray_objectAs :: proc "c" (self: ^MutableArray, index: UInteger, $T: typeid) -> T where intrinsics.type_is_pointer(T), intrinsics.type_is_subtype_of(T, ^Object) { + return (T)(MutableArray_object(self, index)) +} + +@(objc_type=MutableArray, objc_name="count") +MutableArray_count :: proc "c" (self: ^MutableArray) -> UInteger { + return msgSend(UInteger, self, "count") +} + + +@(objc_type=MutableArray, objc_name="exchangeObjectAtIndex") +MutableArray_exchangeObjectAtIndex :: proc "c" (self: ^MutableArray, idx1, idx2: UInteger) { + msgSend(nil, self, "exchangeObjectAtIndex:withObjectAtIndex:", idx1, idx2) +} diff --git a/core/sys/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin index 9a74151b0..747920ab7 100644 --- a/core/sys/darwin/Foundation/NSMenu.odin +++ b/core/sys/darwin/Foundation/NSMenu.odin @@ -2,127 +2,562 @@ package objc_Foundation import "base:builtin" import "base:intrinsics" - -KeyEquivalentModifierFlag :: enum UInteger { - CapsLock = 16, // Set if Caps Lock key is pressed. - Shift = 17, // Set if Shift key is pressed. - Control = 18, // Set if Control key is pressed. - Option = 19, // Set if Option or Alternate key is pressed. - Command = 20, // Set if Command key is pressed. - NumericPad = 21, // Set if any key in the numeric keypad is pressed. - Help = 22, // Set if the Help key is pressed. - Function = 23, // Set if any function key is pressed. -} -KeyEquivalentModifierMask :: distinct bit_set[KeyEquivalentModifierFlag; UInteger] - -// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information. -KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask -@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000) +import "core:c" -MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) - - -@(objc_class="NSMenuItem") -MenuItem :: struct {using _: Object} - -@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true) -MenuItem_alloc :: proc "c" () -> ^MenuItem { - return msgSend(^MenuItem, MenuItem, "alloc") +MenuSelectionMode :: enum c.long { + Automatic = 0, + SelectOne = 1, + SelectAny = 2, } -@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) -MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { - s := string(name) - n := len(s) - sel: SEL - if n > 0 && s[n-1] != ':' { - col_name := intrinsics.alloca(n+2, 1) - builtin.copy(col_name[:n], s) - col_name[n] = ':' - col_name[n+1] = 0 - sel = sel_registerName(cstring(col_name)) - } else { - sel = sel_registerName(name) - } - if callback != nil { - class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@") - } - return sel +MenuPresentationStyle :: enum c.long { + Regular = 0, + Palette = 1, } -@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) -MenuItem_separatorItem :: proc "c" () -> ^MenuItem { - return msgSend(^MenuItem, MenuItem, "separatorItem") +UserInterfaceLayoutDirection :: enum c.long { + LeftToRight = 0, + RightToLeft = 1, } -@(objc_type=MenuItem, objc_name="init") -MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { - return msgSend(^MenuItem, self, "init") +MenuPropertyItem :: enum c.ulong { + Title = 0, + AttributedTitle = 1, + KeyEquivalent = 2, + Image = 3, + Enabled = 4, + AccessibilityDescription = 5, } - -@(objc_type=MenuItem, objc_name="initWithTitle") -MenuItem_initWithTitle :: proc "c" (self: ^MenuItem, title: ^String, action: SEL, keyEquivalent: ^String) -> ^MenuItem { - return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", title, action, keyEquivalent) -} - -@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask") -MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) { - msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask) -} - -@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask") -MenuItem_keyEquivalentModifierMask :: proc "c" (self: ^MenuItem) -> KeyEquivalentModifierMask { - return msgSend(KeyEquivalentModifierMask, self, "keyEquivalentModifierMask") -} - -@(objc_type=MenuItem, objc_name="setSubmenu") -MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) { - msgSend(nil, self, "setSubmenu:", submenu) -} - -@(objc_type=MenuItem, objc_name="title") -MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String { - return msgSend(^String, self, "title") -} - -@(objc_type=MenuItem, objc_name="setTitle") -MenuItem_setTitle :: proc "c" (self: ^MenuItem, title: ^String) -> ^String { - return msgSend(^String, self, "title:", title) -} - +MenuProperties :: distinct bit_set[MenuPropertyItem; c.ulong] @(objc_class="NSMenu") Menu :: struct {using _: Object} -@(objc_type=Menu, objc_name="alloc", objc_is_class_method=true) -Menu_alloc :: proc "c" () -> ^Menu { - return msgSend(^Menu, Menu, "alloc") -} - @(objc_type=Menu, objc_name="init") Menu_init :: proc "c" (self: ^Menu) -> ^Menu { return msgSend(^Menu, self, "init") } + @(objc_type=Menu, objc_name="initWithTitle") -Menu_initWithTitle :: proc "c" (self: ^Menu, title: ^String) -> ^Menu { +Menu_initWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^Menu { return msgSend(^Menu, self, "initWithTitle:", title) } - - +@(objc_type=Menu, objc_name="initWithCoder") +Menu_initWithCoder :: #force_inline proc "c" (self: ^Menu, coder: ^Coder) -> ^Menu { + return msgSend(^Menu, self, "initWithCoder:", coder) +} +@(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView", objc_is_class_method=true) +Menu_popUpContextMenu_withEvent_forView :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View) { + msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:", menu, event, view) +} +// @(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView_withFont", objc_is_class_method=true) +// Menu_popUpContextMenu_withEvent_forView_withFont :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View, font: ^Font) { +// msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:withFont:", menu, event, view, font) +// } +@(objc_type=Menu, objc_name="popUpMenuPositioningItem") +Menu_popUpMenuPositioningItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem, location: Point, view: ^View) -> bool { + return msgSend(bool, self, "popUpMenuPositioningItem:atLocation:inView:", item, location, view) +} +@(objc_type=Menu, objc_name="setMenuBarVisible", objc_is_class_method=true) +Menu_setMenuBarVisible :: #force_inline proc "c" (visible: bool) { + msgSend(nil, Menu, "setMenuBarVisible:", visible) +} +@(objc_type=Menu, objc_name="menuBarVisible", objc_is_class_method=true) +Menu_menuBarVisible :: #force_inline proc "c" () -> bool { + return msgSend(bool, Menu, "menuBarVisible") +} +@(objc_type=Menu, objc_name="insertItem") +Menu_insertItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem, index: Integer) { + msgSend(nil, self, "insertItem:atIndex:", newItem, index) +} @(objc_type=Menu, objc_name="addItem") -Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) { - msgSend(nil, self, "addItem:", item) +Menu_addItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem) { + msgSend(nil, self, "addItem:", newItem) +} +@(objc_type=Menu, objc_name="insertItemWithTitle") +Menu_insertItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String, index: Integer) -> ^MenuItem { + return msgSend(^MenuItem, self, "insertItemWithTitle:action:keyEquivalent:atIndex:", string, selector, charCode, index) } - @(objc_type=Menu, objc_name="addItemWithTitle") -Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem { - return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent) +Menu_addItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem { + return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", string, selector, charCode) +} +@(objc_type=Menu, objc_name="removeItemAtIndex") +Menu_removeItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) { + msgSend(nil, self, "removeItemAtIndex:", index) +} +@(objc_type=Menu, objc_name="removeItem") +Menu_removeItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "removeItem:", item) +} +@(objc_type=Menu, objc_name="setSubmenu") +Menu_setSubmenu :: #force_inline proc "c" (self: ^Menu, menu: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "setSubmenu:forItem:", menu, item) +} +@(objc_type=Menu, objc_name="removeAllItems") +Menu_removeAllItems :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "removeAllItems") +} +@(objc_type=Menu, objc_name="itemAtIndex") +Menu_itemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) -> ^MenuItem { + return msgSend(^MenuItem, self, "itemAtIndex:", index) +} +@(objc_type=Menu, objc_name="indexOfItem") +Menu_indexOfItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) -> Integer { + return msgSend(Integer, self, "indexOfItem:", item) +} +@(objc_type=Menu, objc_name="indexOfItemWithTitle") +Menu_indexOfItemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> Integer { + return msgSend(Integer, self, "indexOfItemWithTitle:", title) +} +@(objc_type=Menu, objc_name="indexOfItemWithTag") +Menu_indexOfItemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> Integer { + return msgSend(Integer, self, "indexOfItemWithTag:", tag) +} +@(objc_type=Menu, objc_name="indexOfItemWithRepresentedObject") +Menu_indexOfItemWithRepresentedObject :: #force_inline proc "c" (self: ^Menu, object: id) -> Integer { + return msgSend(Integer, self, "indexOfItemWithRepresentedObject:", object) +} +@(objc_type=Menu, objc_name="indexOfItemWithSubmenu") +Menu_indexOfItemWithSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Integer { + return msgSend(Integer, self, "indexOfItemWithSubmenu:", submenu) +} +@(objc_type=Menu, objc_name="indexOfItemWithTarget") +Menu_indexOfItemWithTarget :: #force_inline proc "c" (self: ^Menu, target: id, actionSelector: SEL) -> Integer { + return msgSend(Integer, self, "indexOfItemWithTarget:andAction:", target, actionSelector) +} +@(objc_type=Menu, objc_name="itemWithTitle") +Menu_itemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^MenuItem { + return msgSend(^MenuItem, self, "itemWithTitle:", title) +} +@(objc_type=Menu, objc_name="itemWithTag") +Menu_itemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> ^MenuItem { + return msgSend(^MenuItem, self, "itemWithTag:", tag) +} +@(objc_type=Menu, objc_name="update") +Menu_update :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "update") +} +@(objc_type=Menu, objc_name="performKeyEquivalent") +Menu_performKeyEquivalent :: #force_inline proc "c" (self: ^Menu, event: ^Event) -> bool { + return msgSend(bool, self, "performKeyEquivalent:", event) +} +@(objc_type=Menu, objc_name="itemChanged") +Menu_itemChanged :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "itemChanged:", item) +} +@(objc_type=Menu, objc_name="performActionForItemAtIndex") +Menu_performActionForItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) { + msgSend(nil, self, "performActionForItemAtIndex:", index) +} +@(objc_type=Menu, objc_name="cancelTracking") +Menu_cancelTracking :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "cancelTracking") +} +@(objc_type=Menu, objc_name="cancelTrackingWithoutAnimation") +Menu_cancelTrackingWithoutAnimation :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "cancelTrackingWithoutAnimation") +} +@(objc_type=Menu, objc_name="title") +Menu_title :: #force_inline proc "c" (self: ^Menu) -> ^String { + return msgSend(^String, self, "title") +} +@(objc_type=Menu, objc_name="setTitle") +Menu_setTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) { + msgSend(nil, self, "setTitle:", title) +} +@(objc_type=Menu, objc_name="supermenu") +Menu_supermenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu { + return msgSend(^Menu, self, "supermenu") +} +@(objc_type=Menu, objc_name="setSupermenu") +Menu_setSupermenu :: #force_inline proc "c" (self: ^Menu, supermenu: ^Menu) { + msgSend(nil, self, "setSupermenu:", supermenu) +} +@(objc_type=Menu, objc_name="itemArray") +Menu_itemArray :: #force_inline proc "c" (self: ^Menu) -> ^Array { + return msgSend(^Array, self, "itemArray") +} +@(objc_type=Menu, objc_name="setItemArray") +Menu_setItemArray :: #force_inline proc "c" (self: ^Menu, itemArray: ^Array) { + msgSend(nil, self, "setItemArray:", itemArray) +} +@(objc_type=Menu, objc_name="numberOfItems") +Menu_numberOfItems :: #force_inline proc "c" (self: ^Menu) -> Integer { + return msgSend(Integer, self, "numberOfItems") +} +@(objc_type=Menu, objc_name="autoenablesItems") +Menu_autoenablesItems :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "autoenablesItems") +} +@(objc_type=Menu, objc_name="setAutoenablesItems") +Menu_setAutoenablesItems :: #force_inline proc "c" (self: ^Menu, autoenablesItems: bool) { + msgSend(nil, self, "setAutoenablesItems:", autoenablesItems) +} +@(objc_type=Menu, objc_name="delegate") +Menu_delegate :: #force_inline proc "c" (self: ^Menu) -> ^MenuDelegate { + return msgSend(^MenuDelegate, self, "delegate") +} +@(objc_type=Menu, objc_name="setDelegate") +Menu_setDelegate :: #force_inline proc "c" (self: ^Menu, delegate: ^MenuDelegate) { + msgSend(nil, self, "setDelegate:", delegate) +} +@(objc_type=Menu, objc_name="menuBarHeight") +Menu_menuBarHeight :: #force_inline proc "c" (self: ^Menu) -> Float { + return msgSend(Float, self, "menuBarHeight") +} +@(objc_type=Menu, objc_name="highlightedItem") +Menu_highlightedItem :: #force_inline proc "c" (self: ^Menu) -> ^MenuItem { + return msgSend(^MenuItem, self, "highlightedItem") +} +@(objc_type=Menu, objc_name="minimumWidth") +Menu_minimumWidth :: #force_inline proc "c" (self: ^Menu) -> Float { + return msgSend(Float, self, "minimumWidth") +} +@(objc_type=Menu, objc_name="setMinimumWidth") +Menu_setMinimumWidth :: #force_inline proc "c" (self: ^Menu, minimumWidth: Float) { + msgSend(nil, self, "setMinimumWidth:", minimumWidth) +} +@(objc_type=Menu, objc_name="size") +Menu_size :: #force_inline proc "c" (self: ^Menu) -> Size { + return msgSend(Size, self, "size") +} +// @(objc_type=Menu, objc_name="font") +// Menu_font :: #force_inline proc "c" (self: ^Menu) -> ^Font { +// return msgSend(^Font, self, "font") +// } +// @(objc_type=Menu, objc_name="setFont") +// Menu_setFont :: #force_inline proc "c" (self: ^Menu, font: ^Font) { +// msgSend(nil, self, "setFont:", font) +// } +@(objc_type=Menu, objc_name="allowsContextMenuPlugIns") +Menu_allowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "allowsContextMenuPlugIns") +} +@(objc_type=Menu, objc_name="setAllowsContextMenuPlugIns") +Menu_setAllowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu, allowsContextMenuPlugIns: bool) { + msgSend(nil, self, "setAllowsContextMenuPlugIns:", allowsContextMenuPlugIns) +} +@(objc_type=Menu, objc_name="showsStateColumn") +Menu_showsStateColumn :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "showsStateColumn") +} +@(objc_type=Menu, objc_name="setShowsStateColumn") +Menu_setShowsStateColumn :: #force_inline proc "c" (self: ^Menu, showsStateColumn: bool) { + msgSend(nil, self, "setShowsStateColumn:", showsStateColumn) +} +@(objc_type=Menu, objc_name="userInterfaceLayoutDirection") +Menu_userInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu) -> UserInterfaceLayoutDirection { + return msgSend(UserInterfaceLayoutDirection, self, "userInterfaceLayoutDirection") +} +@(objc_type=Menu, objc_name="setUserInterfaceLayoutDirection") +Menu_setUserInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu, userInterfaceLayoutDirection: UserInterfaceLayoutDirection) { + msgSend(nil, self, "setUserInterfaceLayoutDirection:", userInterfaceLayoutDirection) +} +@(objc_type=Menu, objc_name="paletteMenuWithColors_titles_selectionHandler", objc_is_class_method=true) +Menu_paletteMenuWithColors_titles_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu { + return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:selectionHandler:", colors, itemTitles, onSelectionChange) +} +// @(objc_type=Menu, objc_name="paletteMenuWithColors_titles_templateImage_selectionHandler", objc_is_class_method=true) +// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, image: ^Image, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu { +// return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:templateImage:selectionHandler:", colors, itemTitles, image, onSelectionChange) +// } +@(objc_type=Menu, objc_name="presentationStyle") +Menu_presentationStyle :: #force_inline proc "c" (self: ^Menu) -> MenuPresentationStyle { + return msgSend(MenuPresentationStyle, self, "presentationStyle") +} +@(objc_type=Menu, objc_name="setPresentationStyle") +Menu_setPresentationStyle :: #force_inline proc "c" (self: ^Menu, presentationStyle: MenuPresentationStyle) { + msgSend(nil, self, "setPresentationStyle:", presentationStyle) +} +@(objc_type=Menu, objc_name="selectionMode") +Menu_selectionMode :: #force_inline proc "c" (self: ^Menu) -> MenuSelectionMode { + return msgSend(MenuSelectionMode, self, "selectionMode") +} +@(objc_type=Menu, objc_name="setSelectionMode") +Menu_setSelectionMode :: #force_inline proc "c" (self: ^Menu, selectionMode: MenuSelectionMode) { + msgSend(nil, self, "setSelectionMode:", selectionMode) +} +@(objc_type=Menu, objc_name="selectedItems") +Menu_selectedItems :: #force_inline proc "c" (self: ^Menu) -> ^Array { + return msgSend(^Array, self, "selectedItems") +} +@(objc_type=Menu, objc_name="setSelectedItems") +Menu_setSelectedItems :: #force_inline proc "c" (self: ^Menu, selectedItems: ^Array) { + msgSend(nil, self, "setSelectedItems:", selectedItems) +} +@(objc_type=Menu, objc_name="submenuAction") +Menu_submenuAction :: #force_inline proc "c" (self: ^Menu, sender: id) { + msgSend(nil, self, "submenuAction:", sender) +} +@(objc_type=Menu, objc_name="propertiesToUpdate") +Menu_propertiesToUpdate :: #force_inline proc "c" (self: ^Menu) -> MenuProperties { + return msgSend(MenuProperties, self, "propertiesToUpdate") +} +@(objc_type=Menu, objc_name="setMenuRepresentation") +Menu_setMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) { + msgSend(nil, self, "setMenuRepresentation:", menuRep) +} +@(objc_type=Menu, objc_name="menuRepresentation") +Menu_menuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id { + return msgSend(id, self, "menuRepresentation") +} +@(objc_type=Menu, objc_name="setContextMenuRepresentation") +Menu_setContextMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) { + msgSend(nil, self, "setContextMenuRepresentation:", menuRep) +} +@(objc_type=Menu, objc_name="contextMenuRepresentation") +Menu_contextMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id { + return msgSend(id, self, "contextMenuRepresentation") +} +@(objc_type=Menu, objc_name="setTearOffMenuRepresentation") +Menu_setTearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) { + msgSend(nil, self, "setTearOffMenuRepresentation:", menuRep) +} +@(objc_type=Menu, objc_name="tearOffMenuRepresentation") +Menu_tearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id { + return msgSend(id, self, "tearOffMenuRepresentation") +} +@(objc_type=Menu, objc_name="menuZone", objc_is_class_method=true) +Menu_menuZone :: #force_inline proc "c" () -> ^Zone { + return msgSend(^Zone, Menu, "menuZone") +} +@(objc_type=Menu, objc_name="setMenuZone", objc_is_class_method=true) +Menu_setMenuZone :: #force_inline proc "c" (zone: ^Zone) { + msgSend(nil, Menu, "setMenuZone:", zone) +} +@(objc_type=Menu, objc_name="attachedMenu") +Menu_attachedMenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu { + return msgSend(^Menu, self, "attachedMenu") +} +@(objc_type=Menu, objc_name="isAttached") +Menu_isAttached :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "isAttached") +} +@(objc_type=Menu, objc_name="sizeToFit") +Menu_sizeToFit :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "sizeToFit") +} +@(objc_type=Menu, objc_name="locationForSubmenu") +Menu_locationForSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Point { + return msgSend(Point, self, "locationForSubmenu:", submenu) +} +@(objc_type=Menu, objc_name="helpRequested") +Menu_helpRequested :: #force_inline proc "c" (self: ^Menu, eventPtr: ^Event) { + msgSend(nil, self, "helpRequested:", eventPtr) +} +@(objc_type=Menu, objc_name="menuChangedMessagesEnabled") +Menu_menuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "menuChangedMessagesEnabled") +} +@(objc_type=Menu, objc_name="setMenuChangedMessagesEnabled") +Menu_setMenuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu, menuChangedMessagesEnabled: bool) { + msgSend(nil, self, "setMenuChangedMessagesEnabled:", menuChangedMessagesEnabled) +} +@(objc_type=Menu, objc_name="isTornOff") +Menu_isTornOff :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "isTornOff") +} +@(objc_type=Menu, objc_name="load", objc_is_class_method=true) +Menu_load :: #force_inline proc "c" () { + msgSend(nil, Menu, "load") +} +@(objc_type=Menu, objc_name="initialize", objc_is_class_method=true) +Menu_initialize :: #force_inline proc "c" () { + msgSend(nil, Menu, "initialize") +} +@(objc_type=Menu, objc_name="new", objc_is_class_method=true) +Menu_new :: #force_inline proc "c" () -> ^Menu { + return msgSend(^Menu, Menu, "new") +} +@(objc_type=Menu, objc_name="allocWithZone", objc_is_class_method=true) +Menu_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^Menu { + return msgSend(^Menu, Menu, "allocWithZone:", zone) +} +@(objc_type=Menu, objc_name="alloc", objc_is_class_method=true) +Menu_alloc :: #force_inline proc "c" () -> ^Menu { + return msgSend(^Menu, Menu, "alloc") +} +@(objc_type=Menu, objc_name="copyWithZone", objc_is_class_method=true) +Menu_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, Menu, "copyWithZone:", zone) +} +@(objc_type=Menu, objc_name="mutableCopyWithZone", objc_is_class_method=true) +Menu_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, Menu, "mutableCopyWithZone:", zone) +} +@(objc_type=Menu, objc_name="instancesRespondToSelector", objc_is_class_method=true) +Menu_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool { + return msgSend(bool, Menu, "instancesRespondToSelector:", aSelector) +} +@(objc_type=Menu, objc_name="conformsToProtocol", objc_is_class_method=true) +Menu_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool { + return msgSend(bool, Menu, "conformsToProtocol:", protocol) +} +@(objc_type=Menu, objc_name="instanceMethodForSelector", objc_is_class_method=true) +Menu_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP { + return msgSend(IMP, Menu, "instanceMethodForSelector:", aSelector) +} +// @(objc_type=Menu, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true) +// Menu_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature { +// return msgSend(^MethodSignature, Menu, "instanceMethodSignatureForSelector:", aSelector) +// } +@(objc_type=Menu, objc_name="isSubclassOfClass", objc_is_class_method=true) +Menu_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool { + return msgSend(bool, Menu, "isSubclassOfClass:", aClass) +} +@(objc_type=Menu, objc_name="resolveClassMethod", objc_is_class_method=true) +Menu_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, Menu, "resolveClassMethod:", sel) +} +@(objc_type=Menu, objc_name="resolveInstanceMethod", objc_is_class_method=true) +Menu_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, Menu, "resolveInstanceMethod:", sel) +} +@(objc_type=Menu, objc_name="hash", objc_is_class_method=true) +Menu_hash :: #force_inline proc "c" () -> UInteger { + return msgSend(UInteger, Menu, "hash") +} +@(objc_type=Menu, objc_name="superclass", objc_is_class_method=true) +Menu_superclass :: #force_inline proc "c" () -> Class { + return msgSend(Class, Menu, "superclass") +} +@(objc_type=Menu, objc_name="class", objc_is_class_method=true) +Menu_class :: #force_inline proc "c" () -> Class { + return msgSend(Class, Menu, "class") +} +@(objc_type=Menu, objc_name="description", objc_is_class_method=true) +Menu_description :: #force_inline proc "c" () -> ^String { + return msgSend(^String, Menu, "description") +} +@(objc_type=Menu, objc_name="debugDescription", objc_is_class_method=true) +Menu_debugDescription :: #force_inline proc "c" () -> ^String { + return msgSend(^String, Menu, "debugDescription") +} +@(objc_type=Menu, objc_name="version", objc_is_class_method=true) +Menu_version :: #force_inline proc "c" () -> Integer { + return msgSend(Integer, Menu, "version") +} +@(objc_type=Menu, objc_name="setVersion", objc_is_class_method=true) +Menu_setVersion :: #force_inline proc "c" (aVersion: Integer) { + msgSend(nil, Menu, "setVersion:", aVersion) +} +@(objc_type=Menu, objc_name="poseAsClass", objc_is_class_method=true) +Menu_poseAsClass :: #force_inline proc "c" (aClass: Class) { + msgSend(nil, Menu, "poseAsClass:", aClass) +} +@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true) +Menu_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) { + msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument) +} +@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true) +Menu_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) { + msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:", aTarget) +} +@(objc_type=Menu, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true) +Menu_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool { + return msgSend(bool, Menu, "accessInstanceVariablesDirectly") +} +@(objc_type=Menu, objc_name="useStoredAccessor", objc_is_class_method=true) +Menu_useStoredAccessor :: #force_inline proc "c" () -> bool { + return msgSend(bool, Menu, "useStoredAccessor") +} +@(objc_type=Menu, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true) +Menu_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set { + return msgSend(^Set, Menu, "keyPathsForValuesAffectingValueForKey:", key) +} +@(objc_type=Menu, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true) +Menu_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool { + return msgSend(bool, Menu, "automaticallyNotifiesObserversForKey:", key) +} +@(objc_type=Menu, objc_name="setKeys", objc_is_class_method=true) +Menu_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) { + msgSend(nil, Menu, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey) +} +@(objc_type=Menu, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true) +Menu_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array { + return msgSend(^Array, Menu, "classFallbacksForKeyedArchiver") +} +@(objc_type=Menu, objc_name="classForKeyedUnarchiver", objc_is_class_method=true) +Menu_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class { + return msgSend(Class, Menu, "classForKeyedUnarchiver") +} +@(objc_type=Menu, objc_name="exposeBinding", objc_is_class_method=true) +Menu_exposeBinding :: #force_inline proc "c" (binding: ^String) { + msgSend(nil, Menu, "exposeBinding:", binding) +} +@(objc_type=Menu, objc_name="setDefaultPlaceholder", objc_is_class_method=true) +Menu_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) { + msgSend(nil, Menu, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding) +} +@(objc_type=Menu, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true) +Menu_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id { + return msgSend(id, Menu, "defaultPlaceholderForMarker:withBinding:", marker, binding) +} +@(objc_type=Menu, objc_name="popUpContextMenu") +Menu_popUpContextMenu :: proc { + Menu_popUpContextMenu_withEvent_forView, + // Menu_popUpContextMenu_withEvent_forView_withFont, } -@(objc_type=Menu, objc_name="itemArray") -Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array { - return msgSend(^Array, self, "itemArray") -} \ No newline at end of file +@(objc_type=Menu, objc_name="paletteMenuWithColors") +Menu_paletteMenuWithColors :: proc { + Menu_paletteMenuWithColors_titles_selectionHandler, + // Menu_paletteMenuWithColors_titles_templateImage_selectionHandler, +} + +@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget") +Menu_cancelPreviousPerformRequestsWithTarget :: proc { + Menu_cancelPreviousPerformRequestsWithTarget_selector_object, + Menu_cancelPreviousPerformRequestsWithTarget_, +} + + + + + + + +@(objc_class="NSMenuDelegate") +MenuDelegate :: struct {using _: Object, using _: ObjectProtocol} + +@(objc_type=MenuDelegate, objc_name="menuNeedsUpdate") +MenuDelegate_menuNeedsUpdate :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) { + msgSend(nil, self, "menuNeedsUpdate:", menu) +} +@(objc_type=MenuDelegate, objc_name="numberOfItemsInMenu") +MenuDelegate_numberOfItemsInMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) -> Integer { + return msgSend(Integer, self, "numberOfItemsInMenu:", menu) +} +@(objc_type=MenuDelegate, objc_name="menu_updateItem_atIndex_shouldCancel") +MenuDelegate_menu_updateItem_atIndex_shouldCancel :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem, index: Integer, shouldCancel: bool) -> bool { + return msgSend(bool, self, "menu:updateItem:atIndex:shouldCancel:", menu, item, index, shouldCancel) +} +@(objc_type=MenuDelegate, objc_name="menuHasKeyEquivalent") +MenuDelegate_menuHasKeyEquivalent :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, event: ^Event, target: ^id, action: ^SEL) -> bool { + return msgSend(bool, self, "menuHasKeyEquivalent:forEvent:target:action:", menu, event, target, action) +} +@(objc_type=MenuDelegate, objc_name="menuWillOpen") +MenuDelegate_menuWillOpen :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) { + msgSend(nil, self, "menuWillOpen:", menu) +} +@(objc_type=MenuDelegate, objc_name="menuDidClose") +MenuDelegate_menuDidClose :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) { + msgSend(nil, self, "menuDidClose:", menu) +} +@(objc_type=MenuDelegate, objc_name="menu_willHighlightItem") +MenuDelegate_menu_willHighlightItem :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "menu:willHighlightItem:", menu, item) +} +@(objc_type=MenuDelegate, objc_name="confinementRectForMenu") +MenuDelegate_confinementRectForMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, screen: ^Screen) -> Rect { + return msgSend(Rect, self, "confinementRectForMenu:onScreen:", menu, screen) +} +@(objc_type=MenuDelegate, objc_name="menu") +MenuDelegate_menu :: proc { + MenuDelegate_menu_updateItem_atIndex_shouldCancel, + MenuDelegate_menu_willHighlightItem, +} diff --git a/core/sys/darwin/Foundation/NSMenuItem.odin b/core/sys/darwin/Foundation/NSMenuItem.odin new file mode 100644 index 000000000..248a0cf4f --- /dev/null +++ b/core/sys/darwin/Foundation/NSMenuItem.odin @@ -0,0 +1,460 @@ +package objc_Foundation + +import "base:builtin" +import "base:intrinsics" + +KeyEquivalentModifierFlag :: EventModifierFlag +KeyEquivalentModifierMask :: EventModifierFlags + +// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information. +KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask +@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000) + +MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) + +@(objc_class="NSMenuItem") +MenuItem :: struct {using _: Object} + +@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) +MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { + s := string(name) + n := len(s) + sel: SEL + if n > 0 && s[n-1] != ':' { + col_name := intrinsics.alloca(n+2, 1) + builtin.copy(col_name[:n], s) + col_name[n] = ':' + col_name[n+1] = 0 + sel = sel_registerName(cstring(col_name)) + } else { + sel = sel_registerName(name) + } + if callback != nil { + class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@") + } + return sel +} + +@(objc_type=MenuItem, objc_name="init") +MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { + return msgSend(^MenuItem, self, "init") +} + + +@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) +MenuItem_separatorItem :: #force_inline proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "separatorItem") +} +@(objc_type=MenuItem, objc_name="sectionHeaderWithTitle", objc_is_class_method=true) +MenuItem_sectionHeaderWithTitle :: #force_inline proc "c" (title: ^String) -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "sectionHeaderWithTitle:", title) +} +@(objc_type=MenuItem, objc_name="initWithTitle") +MenuItem_initWithTitle :: #force_inline proc "c" (self: ^MenuItem, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem { + return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", string, selector, charCode) +} +@(objc_type=MenuItem, objc_name="initWithCoder") +MenuItem_initWithCoder :: #force_inline proc "c" (self: ^MenuItem, coder: ^Coder) -> ^MenuItem { + return msgSend(^MenuItem, self, "initWithCoder:", coder) +} +@(objc_type=MenuItem, objc_name="usesUserKeyEquivalents", objc_is_class_method=true) +MenuItem_usesUserKeyEquivalents :: #force_inline proc "c" () -> bool { + return msgSend(bool, MenuItem, "usesUserKeyEquivalents") +} +@(objc_type=MenuItem, objc_name="setUsesUserKeyEquivalents", objc_is_class_method=true) +MenuItem_setUsesUserKeyEquivalents :: #force_inline proc "c" (usesUserKeyEquivalents: bool) { + msgSend(nil, MenuItem, "setUsesUserKeyEquivalents:", usesUserKeyEquivalents) +} +@(objc_type=MenuItem, objc_name="menu") +MenuItem_menu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu { + return msgSend(^Menu, self, "menu") +} +@(objc_type=MenuItem, objc_name="setMenu") +MenuItem_setMenu :: #force_inline proc "c" (self: ^MenuItem, menu: ^Menu) { + msgSend(nil, self, "setMenu:", menu) +} +@(objc_type=MenuItem, objc_name="hasSubmenu") +MenuItem_hasSubmenu :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "hasSubmenu") +} +@(objc_type=MenuItem, objc_name="submenu") +MenuItem_submenu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu { + return msgSend(^Menu, self, "submenu") +} +@(objc_type=MenuItem, objc_name="setSubmenu") +MenuItem_setSubmenu :: #force_inline proc "c" (self: ^MenuItem, submenu: ^Menu) { + msgSend(nil, self, "setSubmenu:", submenu) +} +@(objc_type=MenuItem, objc_name="parentItem") +MenuItem_parentItem :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItem { + return msgSend(^MenuItem, self, "parentItem") +} +@(objc_type=MenuItem, objc_name="title") +MenuItem_title :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "title") +} +@(objc_type=MenuItem, objc_name="setTitle") +MenuItem_setTitle :: #force_inline proc "c" (self: ^MenuItem, title: ^String) { + msgSend(nil, self, "setTitle:", title) +} +// @(objc_type=MenuItem, objc_name="attributedTitle") +// MenuItem_attributedTitle :: #force_inline proc "c" (self: ^MenuItem) -> ^AttributedString { +// return msgSend(^AttributedString, self, "attributedTitle") +// } +// @(objc_type=MenuItem, objc_name="setAttributedTitle") +// MenuItem_setAttributedTitle :: #force_inline proc "c" (self: ^MenuItem, attributedTitle: ^AttributedString) { +// msgSend(nil, self, "setAttributedTitle:", attributedTitle) +// } +@(objc_type=MenuItem, objc_name="subtitle") +MenuItem_subtitle :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "subtitle") +} +@(objc_type=MenuItem, objc_name="setSubtitle") +MenuItem_setSubtitle :: #force_inline proc "c" (self: ^MenuItem, subtitle: ^String) { + msgSend(nil, self, "setSubtitle:", subtitle) +} +@(objc_type=MenuItem, objc_name="isSeparatorItem") +MenuItem_isSeparatorItem :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isSeparatorItem") +} +@(objc_type=MenuItem, objc_name="isSectionHeader") +MenuItem_isSectionHeader :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isSectionHeader") +} +@(objc_type=MenuItem, objc_name="keyEquivalent") +MenuItem_keyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "keyEquivalent") +} +@(objc_type=MenuItem, objc_name="setKeyEquivalent") +MenuItem_setKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem, keyEquivalent: ^String) { + msgSend(nil, self, "setKeyEquivalent:", keyEquivalent) +} +@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask") +MenuItem_keyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem) -> EventModifierFlags { + return msgSend(EventModifierFlags, self, "keyEquivalentModifierMask") +} +@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask") +MenuItem_setKeyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem, keyEquivalentModifierMask: EventModifierFlags) { + msgSend(nil, self, "setKeyEquivalentModifierMask:", keyEquivalentModifierMask) +} +@(objc_type=MenuItem, objc_name="userKeyEquivalent") +MenuItem_userKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "userKeyEquivalent") +} +@(objc_type=MenuItem, objc_name="allowsKeyEquivalentWhenHidden") +MenuItem_allowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "allowsKeyEquivalentWhenHidden") +} +@(objc_type=MenuItem, objc_name="setAllowsKeyEquivalentWhenHidden") +MenuItem_setAllowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem, allowsKeyEquivalentWhenHidden: bool) { + msgSend(nil, self, "setAllowsKeyEquivalentWhenHidden:", allowsKeyEquivalentWhenHidden) +} +@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentLocalization") +MenuItem_allowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "allowsAutomaticKeyEquivalentLocalization") +} +@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentLocalization") +MenuItem_setAllowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentLocalization: bool) { + msgSend(nil, self, "setAllowsAutomaticKeyEquivalentLocalization:", allowsAutomaticKeyEquivalentLocalization) +} +@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentMirroring") +MenuItem_allowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "allowsAutomaticKeyEquivalentMirroring") +} +@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentMirroring") +MenuItem_setAllowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentMirroring: bool) { + msgSend(nil, self, "setAllowsAutomaticKeyEquivalentMirroring:", allowsAutomaticKeyEquivalentMirroring) +} +// @(objc_type=MenuItem, objc_name="image") +// MenuItem_image :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "image") +// } +// @(objc_type=MenuItem, objc_name="setImage") +// MenuItem_setImage :: #force_inline proc "c" (self: ^MenuItem, image: ^Image) { +// msgSend(nil, self, "setImage:", image) +// } +// @(objc_type=MenuItem, objc_name="state") +// MenuItem_state :: #force_inline proc "c" (self: ^MenuItem) -> ControlStateValue { +// return msgSend(ControlStateValue, self, "state") +// } +// @(objc_type=MenuItem, objc_name="setState") +// MenuItem_setState :: #force_inline proc "c" (self: ^MenuItem, state: ControlStateValue) { +// msgSend(nil, self, "setState:", state) +// } +// @(objc_type=MenuItem, objc_name="onStateImage") +// MenuItem_onStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "onStateImage") +// } +// @(objc_type=MenuItem, objc_name="setOnStateImage") +// MenuItem_setOnStateImage :: #force_inline proc "c" (self: ^MenuItem, onStateImage: ^Image) { +// msgSend(nil, self, "setOnStateImage:", onStateImage) +// } +// @(objc_type=MenuItem, objc_name="offStateImage") +// MenuItem_offStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "offStateImage") +// } +// @(objc_type=MenuItem, objc_name="setOffStateImage") +// MenuItem_setOffStateImage :: #force_inline proc "c" (self: ^MenuItem, offStateImage: ^Image) { +// msgSend(nil, self, "setOffStateImage:", offStateImage) +// } +// @(objc_type=MenuItem, objc_name="mixedStateImage") +// MenuItem_mixedStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "mixedStateImage") +// } +// @(objc_type=MenuItem, objc_name="setMixedStateImage") +// MenuItem_setMixedStateImage :: #force_inline proc "c" (self: ^MenuItem, mixedStateImage: ^Image) { +// msgSend(nil, self, "setMixedStateImage:", mixedStateImage) +// } +@(objc_type=MenuItem, objc_name="isEnabled") +MenuItem_isEnabled :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isEnabled") +} +@(objc_type=MenuItem, objc_name="setEnabled") +MenuItem_setEnabled :: #force_inline proc "c" (self: ^MenuItem, enabled: bool) { + msgSend(nil, self, "setEnabled:", enabled) +} +@(objc_type=MenuItem, objc_name="isAlternate") +MenuItem_isAlternate :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isAlternate") +} +@(objc_type=MenuItem, objc_name="setAlternate") +MenuItem_setAlternate :: #force_inline proc "c" (self: ^MenuItem, alternate: bool) { + msgSend(nil, self, "setAlternate:", alternate) +} +@(objc_type=MenuItem, objc_name="indentationLevel") +MenuItem_indentationLevel :: #force_inline proc "c" (self: ^MenuItem) -> Integer { + return msgSend(Integer, self, "indentationLevel") +} +@(objc_type=MenuItem, objc_name="setIndentationLevel") +MenuItem_setIndentationLevel :: #force_inline proc "c" (self: ^MenuItem, indentationLevel: Integer) { + msgSend(nil, self, "setIndentationLevel:", indentationLevel) +} +@(objc_type=MenuItem, objc_name="target") +MenuItem_target :: #force_inline proc "c" (self: ^MenuItem) -> id { + return msgSend(id, self, "target") +} +@(objc_type=MenuItem, objc_name="setTarget") +MenuItem_setTarget :: #force_inline proc "c" (self: ^MenuItem, target: id) { + msgSend(nil, self, "setTarget:", target) +} +@(objc_type=MenuItem, objc_name="action") +MenuItem_action :: #force_inline proc "c" (self: ^MenuItem) -> SEL { + return msgSend(SEL, self, "action") +} +@(objc_type=MenuItem, objc_name="setAction") +MenuItem_setAction :: #force_inline proc "c" (self: ^MenuItem, action: SEL) { + msgSend(nil, self, "setAction:", action) +} +@(objc_type=MenuItem, objc_name="tag") +MenuItem_tag :: #force_inline proc "c" (self: ^MenuItem) -> Integer { + return msgSend(Integer, self, "tag") +} +@(objc_type=MenuItem, objc_name="setTag") +MenuItem_setTag :: #force_inline proc "c" (self: ^MenuItem, tag: Integer) { + msgSend(nil, self, "setTag:", tag) +} +@(objc_type=MenuItem, objc_name="representedObject") +MenuItem_representedObject :: #force_inline proc "c" (self: ^MenuItem) -> id { + return msgSend(id, self, "representedObject") +} +@(objc_type=MenuItem, objc_name="setRepresentedObject") +MenuItem_setRepresentedObject :: #force_inline proc "c" (self: ^MenuItem, representedObject: id) { + msgSend(nil, self, "setRepresentedObject:", representedObject) +} +@(objc_type=MenuItem, objc_name="view") +MenuItem_view :: #force_inline proc "c" (self: ^MenuItem) -> ^View { + return msgSend(^View, self, "view") +} +@(objc_type=MenuItem, objc_name="setView") +MenuItem_setView :: #force_inline proc "c" (self: ^MenuItem, view: ^View) { + msgSend(nil, self, "setView:", view) +} +@(objc_type=MenuItem, objc_name="isHighlighted") +MenuItem_isHighlighted :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isHighlighted") +} +@(objc_type=MenuItem, objc_name="isHidden") +MenuItem_isHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isHidden") +} +@(objc_type=MenuItem, objc_name="setHidden") +MenuItem_setHidden :: #force_inline proc "c" (self: ^MenuItem, hidden: bool) { + msgSend(nil, self, "setHidden:", hidden) +} +@(objc_type=MenuItem, objc_name="isHiddenOrHasHiddenAncestor") +MenuItem_isHiddenOrHasHiddenAncestor :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isHiddenOrHasHiddenAncestor") +} +@(objc_type=MenuItem, objc_name="toolTip") +MenuItem_toolTip :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "toolTip") +} +@(objc_type=MenuItem, objc_name="setToolTip") +MenuItem_setToolTip :: #force_inline proc "c" (self: ^MenuItem, toolTip: ^String) { + msgSend(nil, self, "setToolTip:", toolTip) +} +// @(objc_type=MenuItem, objc_name="badge") +// MenuItem_badge :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItemBadge { +// return msgSend(^MenuItemBadge, self, "badge") +// } +// @(objc_type=MenuItem, objc_name="setBadge") +// MenuItem_setBadge :: #force_inline proc "c" (self: ^MenuItem, badge: ^MenuItemBadge) { +// msgSend(nil, self, "setBadge:", badge) +// } +@(objc_type=MenuItem, objc_name="setMnemonicLocation") +MenuItem_setMnemonicLocation :: #force_inline proc "c" (self: ^MenuItem, location: UInteger) { + msgSend(nil, self, "setMnemonicLocation:", location) +} +@(objc_type=MenuItem, objc_name="mnemonicLocation") +MenuItem_mnemonicLocation :: #force_inline proc "c" (self: ^MenuItem) -> UInteger { + return msgSend(UInteger, self, "mnemonicLocation") +} +@(objc_type=MenuItem, objc_name="mnemonic") +MenuItem_mnemonic :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "mnemonic") +} +@(objc_type=MenuItem, objc_name="setTitleWithMnemonic") +MenuItem_setTitleWithMnemonic :: #force_inline proc "c" (self: ^MenuItem, stringWithAmpersand: ^String) { + msgSend(nil, self, "setTitleWithMnemonic:", stringWithAmpersand) +} +@(objc_type=MenuItem, objc_name="load", objc_is_class_method=true) +MenuItem_load :: #force_inline proc "c" () { + msgSend(nil, MenuItem, "load") +} +@(objc_type=MenuItem, objc_name="initialize", objc_is_class_method=true) +MenuItem_initialize :: #force_inline proc "c" () { + msgSend(nil, MenuItem, "initialize") +} +@(objc_type=MenuItem, objc_name="new", objc_is_class_method=true) +MenuItem_new :: #force_inline proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "new") +} +@(objc_type=MenuItem, objc_name="allocWithZone", objc_is_class_method=true) +MenuItem_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "allocWithZone:", zone) +} +@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true) +MenuItem_alloc :: #force_inline proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "alloc") +} +@(objc_type=MenuItem, objc_name="copyWithZone", objc_is_class_method=true) +MenuItem_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, MenuItem, "copyWithZone:", zone) +} +@(objc_type=MenuItem, objc_name="mutableCopyWithZone", objc_is_class_method=true) +MenuItem_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, MenuItem, "mutableCopyWithZone:", zone) +} +@(objc_type=MenuItem, objc_name="instancesRespondToSelector", objc_is_class_method=true) +MenuItem_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool { + return msgSend(bool, MenuItem, "instancesRespondToSelector:", aSelector) +} +@(objc_type=MenuItem, objc_name="conformsToProtocol", objc_is_class_method=true) +MenuItem_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool { + return msgSend(bool, MenuItem, "conformsToProtocol:", protocol) +} +@(objc_type=MenuItem, objc_name="instanceMethodForSelector", objc_is_class_method=true) +MenuItem_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP { + return msgSend(IMP, MenuItem, "instanceMethodForSelector:", aSelector) +} +// @(objc_type=MenuItem, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true) +// MenuItem_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature { +// return msgSend(^MethodSignature, MenuItem, "instanceMethodSignatureForSelector:", aSelector) +// } +@(objc_type=MenuItem, objc_name="isSubclassOfClass", objc_is_class_method=true) +MenuItem_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool { + return msgSend(bool, MenuItem, "isSubclassOfClass:", aClass) +} +@(objc_type=MenuItem, objc_name="resolveClassMethod", objc_is_class_method=true) +MenuItem_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, MenuItem, "resolveClassMethod:", sel) +} +@(objc_type=MenuItem, objc_name="resolveInstanceMethod", objc_is_class_method=true) +MenuItem_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, MenuItem, "resolveInstanceMethod:", sel) +} +@(objc_type=MenuItem, objc_name="hash", objc_is_class_method=true) +MenuItem_hash :: #force_inline proc "c" () -> UInteger { + return msgSend(UInteger, MenuItem, "hash") +} +@(objc_type=MenuItem, objc_name="superclass", objc_is_class_method=true) +MenuItem_superclass :: #force_inline proc "c" () -> Class { + return msgSend(Class, MenuItem, "superclass") +} +@(objc_type=MenuItem, objc_name="class", objc_is_class_method=true) +MenuItem_class :: #force_inline proc "c" () -> Class { + return msgSend(Class, MenuItem, "class") +} +@(objc_type=MenuItem, objc_name="description", objc_is_class_method=true) +MenuItem_description :: #force_inline proc "c" () -> ^String { + return msgSend(^String, MenuItem, "description") +} +@(objc_type=MenuItem, objc_name="debugDescription", objc_is_class_method=true) +MenuItem_debugDescription :: #force_inline proc "c" () -> ^String { + return msgSend(^String, MenuItem, "debugDescription") +} +@(objc_type=MenuItem, objc_name="version", objc_is_class_method=true) +MenuItem_version :: #force_inline proc "c" () -> Integer { + return msgSend(Integer, MenuItem, "version") +} +@(objc_type=MenuItem, objc_name="setVersion", objc_is_class_method=true) +MenuItem_setVersion :: #force_inline proc "c" (aVersion: Integer) { + msgSend(nil, MenuItem, "setVersion:", aVersion) +} +@(objc_type=MenuItem, objc_name="poseAsClass", objc_is_class_method=true) +MenuItem_poseAsClass :: #force_inline proc "c" (aClass: Class) { + msgSend(nil, MenuItem, "poseAsClass:", aClass) +} +@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true) +MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) { + msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument) +} +@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true) +MenuItem_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) { + msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:", aTarget) +} +@(objc_type=MenuItem, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true) +MenuItem_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool { + return msgSend(bool, MenuItem, "accessInstanceVariablesDirectly") +} +@(objc_type=MenuItem, objc_name="useStoredAccessor", objc_is_class_method=true) +MenuItem_useStoredAccessor :: #force_inline proc "c" () -> bool { + return msgSend(bool, MenuItem, "useStoredAccessor") +} +@(objc_type=MenuItem, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true) +MenuItem_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set { + return msgSend(^Set, MenuItem, "keyPathsForValuesAffectingValueForKey:", key) +} +@(objc_type=MenuItem, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true) +MenuItem_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool { + return msgSend(bool, MenuItem, "automaticallyNotifiesObserversForKey:", key) +} +@(objc_type=MenuItem, objc_name="setKeys", objc_is_class_method=true) +MenuItem_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) { + msgSend(nil, MenuItem, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey) +} +@(objc_type=MenuItem, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true) +MenuItem_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array { + return msgSend(^Array, MenuItem, "classFallbacksForKeyedArchiver") +} +@(objc_type=MenuItem, objc_name="classForKeyedUnarchiver", objc_is_class_method=true) +MenuItem_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class { + return msgSend(Class, MenuItem, "classForKeyedUnarchiver") +} +@(objc_type=MenuItem, objc_name="exposeBinding", objc_is_class_method=true) +MenuItem_exposeBinding :: #force_inline proc "c" (binding: ^String) { + msgSend(nil, MenuItem, "exposeBinding:", binding) +} +@(objc_type=MenuItem, objc_name="setDefaultPlaceholder", objc_is_class_method=true) +MenuItem_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) { + msgSend(nil, MenuItem, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding) +} +@(objc_type=MenuItem, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true) +MenuItem_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id { + return msgSend(id, MenuItem, "defaultPlaceholderForMarker:withBinding:", marker, binding) +} +@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget") +MenuItem_cancelPreviousPerformRequestsWithTarget :: proc { + MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object, + MenuItem_cancelPreviousPerformRequestsWithTarget_, +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/objc_helper.odin b/core/sys/darwin/Foundation/objc_helper.odin new file mode 100644 index 000000000..0748d700b --- /dev/null +++ b/core/sys/darwin/Foundation/objc_helper.odin @@ -0,0 +1,136 @@ +package objc_Foundation + +import "base:runtime" +import "base:intrinsics" + +Subclasser_Proc :: proc(cls: Class, vtable: rawptr) + +Object_VTable_Info :: struct { + vtable: rawptr, + size: uint, + impl: Subclasser_Proc, +} + +Class_VTable_Info :: struct { + _context: runtime.Context, + super_vtable: rawptr, + protocol_vtable: rawptr, +} + +@(require_results) +class_get_metaclass :: #force_inline proc "contextless" (cls: Class) -> Class { + return (^Class)(cls)^ +} + +@(require_results) +object_get_vtable_info :: proc "contextless" (obj: id) -> ^Class_VTable_Info { + return (^Class_VTable_Info)(object_getIndexedIvars(obj)) +} + +@(require_results) +make_subclasser :: #force_inline proc(vtable: ^$T, impl: proc(cls: Class, vt: ^T)) -> Object_VTable_Info { + return Object_VTable_Info{ + vtable = vtable, + size = size_of(T), + impl = (Subclasser_Proc)(impl), + } +} + +@(require_results) +register_subclass :: proc( + class_name: cstring, + superclass: Class, + superclass_overrides: Maybe(Object_VTable_Info) = nil, + protocol: Maybe(Object_VTable_Info) = nil, + _context: Maybe(runtime.Context) = nil, +) -> Class { + assert(superclass != nil) + + super_size: uint + proto_size: uint + + if superclass_overrides != nil { + // Align to 8-byte boundary + super_size = (superclass_overrides.?.size + 7)/8 * 8 + } + + if protocol != nil { + // Align to 8-byte boundary + proto_size = (protocol.?.size + 7)/8 * 8 + } + + cls := objc_lookUpClass(class_name) + if cls != nil { + return cls + } + + extra_size := uint(size_of(Class_VTable_Info)) + 8 + super_size + proto_size + + cls = objc_allocateClassPair(superclass, class_name, extra_size) + assert(cls != nil) + + if s, ok := superclass_overrides.?; ok { + s.impl(cls, s.vtable) + } + + if p, ok := protocol.?; ok { + p.impl(cls, p.vtable) + } + + objc_registerClassPair(cls) + meta_cls := class_get_metaclass(cls) + meta_size := uint(class_getInstanceSize(meta_cls)) + + // Offsets are always aligned to 8-byte boundary + info_offset := (meta_size + 7) / 8 * 8 + super_vtable_offset := (info_offset + size_of(Class_VTable_Info) + 7) / 8 * 8 + ptoto_vtable_offset := super_vtable_offset + super_size + + + p_info := (^Class_VTable_Info)(([^]u8)(cls)[info_offset:]) + p_super_vtable := ([^]u8)(cls)[super_vtable_offset:] + p_proto_vtable := ([^]u8)(cls)[ptoto_vtable_offset:] + + intrinsics.mem_zero(p_info, size_of(Class_VTable_Info)) + + // Assign the context + p_info._context = _context.? or_else context + + if s, ok := superclass_overrides.?; ok { + p_info.super_vtable = p_super_vtable + intrinsics.mem_copy(p_super_vtable, s.vtable, super_size) + } + if p, ok := protocol.?; ok { + p_info.protocol_vtable = p_proto_vtable + intrinsics.mem_copy(p_proto_vtable, p.vtable, p.size) + } + + return cls +} + +@(require_results) +class_get_vtable_info :: proc "contextless" (cls: Class) -> ^Class_VTable_Info { + meta_cls := class_get_metaclass(cls) + meta_size := uint(class_getInstanceSize(meta_cls)) + + // Align to 8-byte boundary + info_offset := (meta_size+7) / 8 * 8 + + p_cls := ([^]u8)(cls)[info_offset:] + ctx := (^Class_VTable_Info)(p_cls) + return ctx +} + +@(require_results) +alloc_user_object :: proc "contextless" (cls: Class, _context: Maybe(runtime.Context) = nil) -> id { + info := class_get_vtable_info(cls) + + obj := class_createInstance(cls, size_of(Class_VTable_Info)) + obj_info := (^Class_VTable_Info)(object_getIndexedIvars(obj)) + obj_info^ = info^ + + if _context != nil { + obj_info._context = _context.? + } + return obj +} \ No newline at end of file diff --git a/core/sys/darwin/copyfile.odin b/core/sys/darwin/copyfile.odin new file mode 100644 index 000000000..6c58b8067 --- /dev/null +++ b/core/sys/darwin/copyfile.odin @@ -0,0 +1,67 @@ +package darwin + +import "core:sys/posix" + +copyfile_state_t :: distinct rawptr + +copyfile_flags :: bit_set[enum { + ACL, + STAT, + XATTR, + DATA, + + RECURSIVE = 15, + + CHECK, + EXCL, + NOFOLLOW_SRC, + NOFOLLOW_DST, + MOVE, + UNLINK, + PACK, + UNPACK, + + CLONE, + CLONE_FORCE, + RUN_IN_PLACE, + DATA_SPARSE, + PRESERVE_DST_TRACKED, + VERBOSE = 30, +}; u32] + +COPYFILE_SECURITY :: copyfile_flags{.STAT, .ACL} +COPYFILE_METADATA :: COPYFILE_SECURITY + copyfile_flags{.XATTR} +COPYFILE_ALL :: COPYFILE_METADATA + copyfile_flags{.DATA} + +COPYFILE_NOFOLLOW :: copyfile_flags{.NOFOLLOW_SRC, .NOFOLLOW_DST} + +copyfile_state_flag :: enum u32 { + SRC_FD = 1, + SRC_FILENAME, + DST_FD, + DST_FILENAME, + QUARANTINE, + STATUS_CB, + STATUS_CTX, + COPIED, + XATTRNAME, + WAS_CLONED, + SRC_BSIZE, + DST_BSIZE, + BSIZE, + FORBID_CROSS_MOUNT, + NOCPROTECT, + PRESERVE_SUID, + RECURSIVE_SRC_FTSENT, + FORBID_DST_EXISTING_SYMLINKS, +} + +foreign system { + copyfile :: proc(from, to: cstring, state: copyfile_state_t, flags: copyfile_flags) -> i32 --- + fcopyfile :: proc(from, to: posix.FD, state: copyfile_state_t, flags: copyfile_flags) -> i32 --- + + copyfile_state_alloc :: proc() -> copyfile_state_t --- + copyfile_state_free :: proc(state: copyfile_state_t) -> posix.result --- + copyfile_state_get :: proc(state: copyfile_state_t, flag: copyfile_state_flag, dst: rawptr) -> posix.result --- + copyfile_state_set :: proc(state: copyfile_state_t, flag: copyfile_state_flag, src: rawptr) -> posix.result --- +} diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin index d109f5544..96cfc7be6 100644 --- a/core/sys/darwin/darwin.odin +++ b/core/sys/darwin/darwin.odin @@ -3,6 +3,7 @@ package darwin import "core:c" +@(export) foreign import system "system:System.framework" Bool :: b8 diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin index 58fc7c9e4..6d68dc8f8 100644 --- a/core/sys/darwin/sync.odin +++ b/core/sys/darwin/sync.odin @@ -1,7 +1,5 @@ package darwin -foreign import system "system:System.framework" - // #define OS_WAIT_ON_ADDR_AVAILABILITY \ // __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) when ODIN_OS == .Darwin { diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index 1188091a9..6376949f4 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -19,16 +19,6 @@ X_OK :: c.int((1 << 0)) /* test for execute or search permission */ W_OK :: c.int((1 << 1)) /* test for write permission */ R_OK :: c.int((1 << 2)) /* test for read permission */ -/* copyfile flags */ -COPYFILE_ACL :: (1 << 0) -COPYFILE_STAT :: (1 << 1) -COPYFILE_XATTR :: (1 << 2) -COPYFILE_DATA :: (1 << 3) - -COPYFILE_SECURITY :: (COPYFILE_STAT | COPYFILE_ACL) -COPYFILE_METADATA :: (COPYFILE_SECURITY | COPYFILE_XATTR) -COPYFILE_ALL :: (COPYFILE_METADATA | COPYFILE_DATA) - /* syslimits.h */ PATH_MAX :: 1024 /* max bytes in pathname */ diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 53660dc8f..d4edf354b 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -579,7 +579,7 @@ Inotify_Event_Bits :: enum u32 { /* Bits for Mem_Protection bitfield */ -Mem_Protection_Bits :: enum{ +Mem_Protection_Bits :: enum { READ = 0, WRITE = 1, EXEC = 2, @@ -594,11 +594,13 @@ Mem_Protection_Bits :: enum{ /* Bits for Map_Flags + + See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al. */ Map_Flags_Bits :: enum { SHARED = 0, PRIVATE = 1, - SHARED_VALIDATE = 2, + DROPPABLE = 3, FIXED = 4, ANONYMOUS = 5, // platform-dependent section start diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index b3bbcafb3..1010c931a 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -373,3 +373,22 @@ PTRACE_SECCOMP_GET_FILTER :: PTrace_Seccomp_Get_Filter_Type(.SECCOMP_GET_FIL PTRACE_SECCOMP_GET_METADATA :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA) PTRACE_GET_SYSCALL_INFO :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO) PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION) + +MAP_SHARED_VALIDATE :: Map_Flags{.SHARED, .PRIVATE} + +MAP_HUGE_SHIFT :: 26 +MAP_HUGE_MASK :: 63 + +MAP_HUGE_16KB :: transmute(Map_Flags)(u32(14) << MAP_HUGE_SHIFT) +MAP_HUGE_64KB :: transmute(Map_Flags)(u32(16) << MAP_HUGE_SHIFT) +MAP_HUGE_512KB :: transmute(Map_Flags)(u32(19) << MAP_HUGE_SHIFT) +MAP_HUGE_1MB :: transmute(Map_Flags)(u32(20) << MAP_HUGE_SHIFT) +MAP_HUGE_2MB :: transmute(Map_Flags)(u32(21) << MAP_HUGE_SHIFT) +MAP_HUGE_8MB :: transmute(Map_Flags)(u32(23) << MAP_HUGE_SHIFT) +MAP_HUGE_16MB :: transmute(Map_Flags)(u32(24) << MAP_HUGE_SHIFT) +MAP_HUGE_32MB :: transmute(Map_Flags)(u32(25) << MAP_HUGE_SHIFT) +MAP_HUGE_256MB :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT) +MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT) +MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT) +MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT) +MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT) \ No newline at end of file diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 8f2284f56..08e0026d3 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -288,7 +288,7 @@ Rename_Flags :: bit_set[Rename_Flags_Bits; u32] /* Directory entry record. - Recommended iterate these with `dirent_iterator()`, + Recommended to iterate these with `dirent_iterate_buf()`, and obtain the name via `dirent_name()`. */ Dirent :: struct { @@ -368,6 +368,8 @@ Mem_Protection :: bit_set[Mem_Protection_Bits; i32] /* Flags for mmap. + + See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al. */ Map_Flags :: bit_set[Map_Flags_Bits; i32] diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 7b0dc61e2..53eb80f86 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -54,22 +54,45 @@ WCOREDUMP :: #force_inline proc "contextless" (s: u32) -> bool { // TODO: sigaddset etc -/// Iterate the results of getdents -/// Only iterates as much data as loaded in the buffer -/// In case you need to iterate *all* files in a directory -/// consider using dirent_get_iterate -/// -/// Example of using dirent_iterate_buf -/// // Get dirents into a buffer -/// buf: [128]u8 -/// sys.getdents(dirfd, buf[:]) -/// // Print the names of the files -/// for dir in sys.dirent_iterate_buf(buf[:], &offs) { -/// name := sys.dirent_name(dir) -/// fmt.println(name) -/// } -/// This function doesn't automatically make a request -/// for the buffer to be refilled +/* +Iterate the results of `getdents()`. + +This procedure extracts a directory entry from `buf` at the offset `offs`. +`offs` will be modified to store an offset to the possible next directory entry +in `buf`. The procedure only iterates as much data as loaded in the buffer and +does not automatically make a request for the buffer to be refilled. + +Inputs: +- buf: A byte buffer with data from `getdents()` +- offs: An offset to the next possible directory entry in `buf` + +Returns: +- A pointer to a directory entry in `buf`, or `nil` +- A bool value denoting if a valid directory entry is returned + +Example: + + import "core:fmt" + import "core:sys/linux" + + print_names :: proc(dirfd: linux.Fd) { + // Get dirents into a buffer. + buf: [128]u8 + // Loop until there are no more entries. + for { + written, err := linux.getdents(dirfd, buf[:]) + if err != .NONE || written == 0 { + break + } + // Print the names of the files. + offset : int + for dir in linux.dirent_iterate_buf(buf[:written], &offset) { + name := linux.dirent_name(dir) + fmt.println(name) + } + } + } +*/ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, cont: bool) { // Stopped iterating when there's no space left if offs^ >= len(buf) { @@ -82,8 +105,17 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, return dirent, true } -/// Obtain the name of dirent as a string -/// The lifetime of the string is bound to the lifetime of the provided dirent structure +/* +Obtain the name of dirent as a string. + +The lifetime of the returned string is bound to the lifetime of the provided dirent structure. + +Inputs: +- dirent: A directory entry + +Returns: +- A name of the entry +*/ dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { str := ([^]u8)(&dirent.name) // Dirents are aligned to 8 bytes, so there is guaranteed to be a null diff --git a/core/sys/windows/scan_codes.odin b/core/sys/windows/scan_codes.odin new file mode 100644 index 000000000..54949c2f6 --- /dev/null +++ b/core/sys/windows/scan_codes.odin @@ -0,0 +1,172 @@ +#+build windows +package sys_windows + +// Win32 scan codes for QWERTY layout +// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes + +KB_SYS_POWERDOWN :: 0xE05E +KB_SYS_SLEEP :: 0xE05F +KB_SYS_WAKEUP :: 0xE063 +KB_ERR_ROLLOVER :: 0x00FF + +KB_A :: 0x001E +KB_B :: 0x0030 +KB_C :: 0x002E +KB_D :: 0x0020 +KB_E :: 0x0012 +KB_F :: 0x0021 +KB_G :: 0x0022 +KB_H :: 0x0023 +KB_I :: 0x0017 +KB_J :: 0x0024 +KB_K :: 0x0025 +KB_L :: 0x0026 +KB_M :: 0x0032 +KB_N :: 0x0031 +KB_O :: 0x0018 +KB_P :: 0x0019 +KB_Q :: 0x0010 +KB_R :: 0x0013 +KB_S :: 0x001F +KB_T :: 0x0014 +KB_U :: 0x0016 +KB_V :: 0x002F +KB_W :: 0x0011 +KB_X :: 0x002D +KB_Y :: 0x0015 +KB_Z :: 0x002C + +KB_1_BANG :: 0x0002 +KB_2_AT :: 0x0003 +KB_3_HASH :: 0x0004 +KB_4_DOLLAR :: 0x0005 +KB_5_PERCENT :: 0x0006 +KB_6_CARET :: 0x0007 +KB_7_AMPERSAND :: 0x0008 +KB_8_STAR :: 0x0009 +KB_9_LEFTBRACKET :: 0x000A +KB_0_RIGHTBRACKET :: 0x000B + +KB_RETURN_ENTER :: 0x001C +KB_ESCAPE :: 0x0001 +KB_DELETE :: 0x000E +KB_TAB :: 0x000F +KB_SPACEBAR :: 0x0039 +KB_DASH_UNDERSCORE :: 0x000C +KB_EQUALS_PLUS :: 0x000D +KB_LEFTBRACE :: 0x001A +KB_RIGHTBRACE :: 0x001B +KB_PIPE_SLASH :: 0x002B +KB_NONUS :: 0x002B +KB_SEMICOLON_COLON :: 0x0027 +KB_APOSTR_DOUBLEQUOT :: 0x0028 +KB_GRAVEACC_TILDE :: 0x0029 +KB_COMMA :: 0x0033 +KB_PERIOD :: 0x0034 +KB_QUESTIONMARK :: 0x0035 +KB_CAPSLOCK :: 0x003A + +KB_F1 :: 0x003B +KB_F2 :: 0x003C +KB_F3 :: 0x003D +KB_F4 :: 0x003E +KB_F5 :: 0x003F +KB_F6 :: 0x0040 +KB_F7 :: 0x0041 +KB_F8 :: 0x0042 +KB_F9 :: 0x0043 +KB_F10 :: 0x0044 +KB_F11 :: 0x0057 +KB_F12 :: 0x0058 + +KB_PRINTSCREEN :: 0xE037 +KB_SCROLLLOCK :: 0x0046 +KB_PAUSE :: 0xE11D45 +KB_INSERT :: 0xE052 +KB_HOME :: 0xE047 +KB_PAGEUP :: 0xE049 +KB_DELETEFORWARD :: 0xE053 +KB_END :: 0xE04F +KB_PAGEDOWN :: 0xE051 +KB_RIGHTARROW :: 0xE04D +KB_LEFTARROW :: 0xE04B +KB_DOWNARROW :: 0xE050 +KB_UPARROW :: 0xE048 + +KP_NUMLOCK_CLEAR :: 0x0045 +KP_FORWARDSLASH :: 0xE035 +KP_STAR :: 0x0037 +KP_DASH :: 0x004A +KP_PLUS :: 0x004E +KP_ENTER :: 0xE01C +KP_1_END :: 0x004F +KP_2_DOWNARROW :: 0x0050 +KP_3_PAGEDN :: 0x0051 +KP_4_LEFTARROW :: 0x004B +KP_5 :: 0x004C +KP_6_RIGHTARROW :: 0x004D +KP_7_HOME :: 0x0047 +KP_8_UPARROW :: 0x0048 +KP_9_PAGEUP :: 0x0049 +KP_0_INSERT :: 0x0052 +KP_PERIOD :: 0x0053 + +KB_NONUS_SLASHBAR :: 0x0056 +KB_APPLICATION :: 0xE05D +KB_POWER :: 0xE05E +KB_EQUALS :: 0x0059 +KB_F13 :: 0x0064 +KB_F14 :: 0x0065 +KB_F15 :: 0x0066 +KB_F16 :: 0x0067 +KB_F17 :: 0x0068 +KB_F18 :: 0x0069 +KB_F19 :: 0x006A +KB_F20 :: 0x006B +KB_F21 :: 0x006C +KB_F22 :: 0x006D +KB_F23 :: 0x006E +KB_F24 :: 0x0076 + +KP_COMMA :: 0x007E + +KB_INTERNATIONAL1 :: 0x0073 +KB_INTERNATIONAL2 :: 0x0070 +KB_INTERNATIONAL3 :: 0x007D +KB_INTERNATIONAL4 :: 0x0079 +KB_INTERNATIONAL5 :: 0x007B +KB_INTERNATIONAL6 :: 0x005C + +KB_LANG1 :: 0x0072 +KB_LANG2 :: 0x0071 +KB_LANG3 :: 0x0078 +KB_LANG4 :: 0x0077 +KB_LANG5 :: 0x0076 + +KB_LEFTCONTROL :: 0x001D +KB_LEFTSHIFT :: 0x002A +KB_LEFTALT :: 0x0038 +KB_LEFTGUI :: 0xE05B +KB_RIGHTCONTROL :: 0xE01D +KB_RIGHTSHIFT :: 0x0036 +KB_RIGHTALT :: 0xE038 +KB_RIGHTGUI :: 0xE05C + +FN_SCANNEXTTRACK :: 0xE019 +FN_SCANPREVTRACK :: 0xE010 +FN_STOP :: 0xE024 +FN_PLAY_PAUSE :: 0xE022 +FN_MUTE :: 0xE020 +FN_VOLUMEINC :: 0xE030 +FN_VOLUMEDEC :: 0xE02E +FN_AL_CONSUMERCTRLCONFIG :: 0xE06D +FN_AL_EMAILREADER :: 0xE06C +FN_AL_CALCULATOR :: 0xE021 +FN_AL_LOCALMACHINEBROWSER :: 0xE06B +FN_AC_SEARCH :: 0xE065 +FN_AC_HOME :: 0xE032 +FN_AC_BACK :: 0xE06A +FN_AC_FORWARD :: 0xE069 +FN_AC_STOP :: 0xE068 +FN_AC_REFRESH :: 0xE067 +FN_AC_BOOKMARKS :: 0xE066 diff --git a/core/encoding/ansi/ansi.odin b/core/terminal/ansi/ansi.odin similarity index 100% rename from core/encoding/ansi/ansi.odin rename to core/terminal/ansi/ansi.odin diff --git a/core/encoding/ansi/doc.odin b/core/terminal/ansi/doc.odin similarity index 100% rename from core/encoding/ansi/doc.odin rename to core/terminal/ansi/doc.odin diff --git a/core/terminal/doc.odin b/core/terminal/doc.odin new file mode 100644 index 000000000..490e9d398 --- /dev/null +++ b/core/terminal/doc.odin @@ -0,0 +1,4 @@ +/* +This package is for interacting with the command line interface of the system. +*/ +package terminal diff --git a/core/terminal/internal.odin b/core/terminal/internal.odin new file mode 100644 index 000000000..485f6868d --- /dev/null +++ b/core/terminal/internal.odin @@ -0,0 +1,87 @@ +#+private +package terminal + +import "core:os" +import "core:strings" + +// Reference documentation: +// +// - [[ https://no-color.org/ ]] +// - [[ https://github.com/termstandard/colors ]] +// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] + +get_no_color :: proc() -> bool { + if no_color, ok := os.lookup_env("NO_COLOR"); ok { + defer delete(no_color) + return no_color != "" + } + return false +} + +get_environment_color :: proc() -> Color_Depth { + // `COLORTERM` is non-standard but widespread and unambiguous. + if colorterm, ok := os.lookup_env("COLORTERM"); ok { + defer delete(colorterm) + // These are the only values that are typically advertised that have + // anything to do with color depth. + if colorterm == "truecolor" || colorterm == "24bit" { + return .True_Color + } + } + + if term, ok := os.lookup_env("TERM"); ok { + defer delete(term) + if strings.contains(term, "-truecolor") { + return .True_Color + } + if strings.contains(term, "-256color") { + return .Eight_Bit + } + if strings.contains(term, "-16color") { + return .Four_Bit + } + + // The `terminfo` database, which is stored in binary on *nix + // platforms, has an undocumented format that is not guaranteed to be + // portable, so beyond this point, we can only make safe assumptions. + // + // This section should only be necessary for terminals that do not + // define any of the previous environment values. + // + // Only a small sampling of some common values are checked here. + switch term { + case "ansi": fallthrough + case "konsole": fallthrough + case "putty": fallthrough + case "rxvt": fallthrough + case "rxvt-color": fallthrough + case "screen": fallthrough + case "st": fallthrough + case "tmux": fallthrough + case "vte": fallthrough + case "xterm": fallthrough + case "xterm-color": + return .Three_Bit + } + } + + return .None +} + +@(init) +init_terminal :: proc() { + _init_terminal() + + // We respect `NO_COLOR` specifically as a color-disabler but not as a + // blanket ban on any terminal manipulation codes, hence why this comes + // after `_init_terminal` which will allow Windows to enable Virtual + // Terminal Processing for non-color control sequences. + if !get_no_color() { + color_enabled = color_depth > .None + } +} + +@(fini) +fini_terminal :: proc() { + _fini_terminal() +} diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin new file mode 100644 index 000000000..1e5566295 --- /dev/null +++ b/core/terminal/terminal.odin @@ -0,0 +1,36 @@ +package terminal + +import "core:os" + +/* +This describes the range of colors that a terminal is capable of supporting. +*/ +Color_Depth :: enum { + None, // No color support + Three_Bit, // 8 colors + Four_Bit, // 16 colors + Eight_Bit, // 256 colors + True_Color, // 24-bit true color +} + +/* +Returns true if the file `handle` is attached to a terminal. + +This is normally true for `os.stdout` and `os.stderr` unless they are +redirected to a file. +*/ +@(require_results) +is_terminal :: proc(handle: os.Handle) -> bool { + return _is_terminal(handle) +} + +/* +This is true if the terminal is accepting any form of colored text output. +*/ +color_enabled: bool + +/* +This value reports the color depth support as reported by the terminal at the +start of the program. +*/ +color_depth: Color_Depth diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin new file mode 100644 index 000000000..f578e12c6 --- /dev/null +++ b/core/terminal/terminal_posix.odin @@ -0,0 +1,16 @@ +#+private +#+build linux, darwin, netbsd, openbsd, freebsd, haiku +package terminal + +import "core:os" +import "core:sys/posix" + +_is_terminal :: proc(handle: os.Handle) -> bool { + return bool(posix.isatty(posix.FD(handle))) +} + +_init_terminal :: proc() { + color_depth = get_environment_color() +} + +_fini_terminal :: proc() { } diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin new file mode 100644 index 000000000..18ec98332 --- /dev/null +++ b/core/terminal/terminal_windows.odin @@ -0,0 +1,60 @@ +#+private +package terminal + +import "core:os" +import "core:sys/windows" + +_is_terminal :: proc(handle: os.Handle) -> bool { + is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR + return is_tty +} + +old_modes: [2]struct{ + handle: windows.DWORD, + mode: windows.DWORD, +} = { + {windows.STD_OUTPUT_HANDLE, 0}, + {windows.STD_ERROR_HANDLE, 0}, +} + +@(init) +_init_terminal :: proc() { + vtp_enabled: bool + + for &v in old_modes { + handle := windows.GetStdHandle(v.handle) + if handle == windows.INVALID_HANDLE || handle == nil { + return + } + if windows.GetConsoleMode(handle, &v.mode) { + windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + new_mode: windows.DWORD + windows.GetConsoleMode(handle, &new_mode) + + if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 { + vtp_enabled = true + } + } + } + + if vtp_enabled { + // This color depth is available on Windows 10 since build 10586. + color_depth = .Four_Bit + } else { + // The user may be on a non-default terminal emulator. + color_depth = get_environment_color() + } +} + +@(fini) +_fini_terminal :: proc() { + for v in old_modes { + handle := windows.GetStdHandle(v.handle) + if handle == windows.INVALID_HANDLE || handle == nil { + return + } + + windows.SetConsoleMode(handle, v.mode) + } +} diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index 6752cd79b..7c7eb7b2d 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -10,12 +10,12 @@ package testing */ import "base:runtime" -import "core:encoding/ansi" import "core:fmt" import "core:io" import "core:mem" import "core:path/filepath" import "core:strings" +import "core:terminal/ansi" // Definitions of colors for use in the test runner. SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 83a5ac4e7..56d561d3d 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -13,7 +13,6 @@ package testing import "base:intrinsics" import "base:runtime" import "core:bytes" -import "core:encoding/ansi" @require import "core:encoding/base64" @require import "core:encoding/json" import "core:fmt" @@ -25,6 +24,8 @@ import "core:os" import "core:slice" @require import "core:strings" import "core:sync/chan" +import "core:terminal" +import "core:terminal/ansi" import "core:thread" import "core:time" @@ -44,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S // The format is: `package.test_name,test_name_only,...` TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. +// This requires terminal color support, as well as STDOUT to not be redirected to a file. FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) @@ -70,6 +72,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { } } +@(private) global_log_colors_disabled: bool +@(private) global_ansi_disabled: bool + JSON :: struct { total: int, success: int, @@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) { context.assertion_failure_proc = test_assertion_failure_proc + logger_options := Default_Test_Logger_Opts + if global_log_colors_disabled { + logger_options -= {.Terminal_Color} + } + context.logger = { procedure = test_logger_proc, data = &data.t, lowest_level = get_log_level(), - options = Default_Test_Logger_Opts, + options = logger_options, } random_generator_state: runtime.Default_Random_State @@ -204,13 +214,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - when ODIN_OS == .Windows { - console_ansi_init() - } - stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) + // The animations are only ever shown through STDOUT; + // STDERR is used exclusively for logging regardless of error level. + global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr) + global_ansi_disabled = !terminal.is_terminal(os.stdout) + + should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled + // -- Prepare test data. alloc_error: mem.Allocator_Error @@ -268,12 +281,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { total_done_count := 0 total_test_count := len(internal_tests) - when !FANCY_OUTPUT { - // This is strictly for updating the window title when the progress - // report is disabled. We're otherwise able to depend on the call to - // `needs_to_redraw`. - last_done_count := -1 - } + + // This is strictly for updating the window title when the progress + // report is disabled. We're otherwise able to depend on the call to + // `needs_to_redraw`. + last_done_count := -1 + if total_test_count == 0 { // Exit early. @@ -342,31 +355,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error) defer destroy_report(&report) - when FANCY_OUTPUT { - // We cannot make use of the ANSI save/restore cursor codes, because they - // work by absolute screen coordinates. This will cause unnecessary - // scrollback if we print at the bottom of someone's terminal. - ansi_redraw_string := fmt.aprintf( - // ANSI for "go up N lines then erase the screen from the cursor forward." - ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + - // We'll combine this with the window title format string, since it - // can be printed at the same time. - "%s", - // 1 extra line for the status bar. - 1 + len(report.packages), OSC_WINDOW_TITLE) - assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") - defer delete(ansi_redraw_string) - thread_count_status_string: string = --- - { - PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + // We cannot make use of the ANSI save/restore cursor codes, because they + // work by absolute screen coordinates. This will cause unnecessary + // scrollback if we print at the bottom of someone's terminal. + ansi_redraw_string := fmt.aprintf( + // ANSI for "go up N lines then erase the screen from the cursor forward." + ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + + // We'll combine this with the window title format string, since it + // can be printed at the same time. + "%s", + // 1 extra line for the status bar. + 1 + len(report.packages), OSC_WINDOW_TITLE) + assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") + defer delete(ansi_redraw_string) - unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") - thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) - assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") - } - defer delete(thread_count_status_string) + thread_count_status_string: string = --- + { + PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + + unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") + thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) + assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") } + defer delete(thread_count_status_string) + task_data_slots: []Task_Data = --- task_data_slots, alloc_error = make([]Task_Data, thread_count) @@ -442,11 +455,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // digging through the source to divine everywhere it is used for that. shared_log_allocator := context.allocator + logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure} + if global_log_colors_disabled { + logger_options -= {.Terminal_Color} + } + context.logger = { procedure = runner_logger_proc, data = &log_messages, lowest_level = get_log_level(), - options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, + options = logger_options, } run_index: int @@ -481,11 +499,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { setup_signal_handler() - fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + if !global_ansi_disabled { + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + } - when FANCY_OUTPUT { - signals_were_raised := false + signals_were_raised := false + if should_show_animations { redraw_report(stdout, report) draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } @@ -703,22 +723,22 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { break main_loop } - when FANCY_OUTPUT { - // Because the bounds checking procs send directly to STDERR with - // no way to redirect or handle them, we need to at least try to - // let the user see those messages when using the animated progress - // report. This flag may be set by the block of code below if a - // signal is raised. - // - // It'll be purely by luck if the output is interleaved properly, - // given the nature of non-thread-safe printing. - // - // At worst, if Odin did not print any error for this signal, we'll - // just re-display the progress report. The fatal log error message - // should be enough to clue the user in that something dire has - // occurred. - bypass_progress_overwrite := false - } + + // Because the bounds checking procs send directly to STDERR with + // no way to redirect or handle them, we need to at least try to + // let the user see those messages when using the animated progress + // report. This flag may be set by the block of code below if a + // signal is raised. + // + // It'll be purely by luck if the output is interleaved properly, + // given the nature of non-thread-safe printing. + // + // At worst, if Odin did not print any error for this signal, we'll + // just re-display the progress report. The fatal log error message + // should be enough to clue the user in that something dire has + // occurred. + bypass_progress_overwrite := false + if test_index, reason, ok := should_stop_test(); ok { #no_bounds_check report.all_test_states[test_index] = .Failed @@ -752,7 +772,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) } - when FANCY_OUTPUT { + if should_show_animations { bypass_progress_overwrite = true signals_were_raised = true } @@ -766,7 +786,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // -- Redraw. - when FANCY_OUTPUT { + if should_show_animations { if len(log_messages) == 0 && !needs_to_redraw(report) { continue main_loop } @@ -776,7 +796,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } else { if total_done_count != last_done_count { - fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + if !global_ansi_disabled { + fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + } last_done_count = total_done_count } @@ -801,7 +823,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { clear(&log_messages) bytes.buffer_reset(&batch_buffer) - when FANCY_OUTPUT { + if should_show_animations { redraw_report(batch_writer, report) draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) @@ -822,7 +844,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { finished_in := time.since(start_time) - when !FANCY_OUTPUT { + if !should_show_animations || !terminal.is_terminal(os.stderr) { // One line to space out the results, since we don't have the status // bar in plain mode. fmt.wprintln(batch_writer) @@ -836,24 +858,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { if total_done_count != total_test_count { not_run_count := total_test_count - total_done_count + message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone." fmt.wprintf(batch_writer, - " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", + message, not_run_count, "test was" if not_run_count == 1 else "tests were") } if total_success_count == total_test_count { + message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET fmt.wprintfln(batch_writer, - " %s " + SGR_SUCCESS + "successful." + SGR_RESET, + message, "The test was" if total_test_count == 1 else "All tests were") } else if total_failure_count > 0 { if total_failure_count == total_test_count { + message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET fmt.wprintfln(batch_writer, - " %s " + SGR_FAILED + "failed." + SGR_RESET, + message, "The test" if total_test_count == 1 else "All tests") } else { + message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed." fmt.wprintfln(batch_writer, - " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", + message, total_failure_count, "" if total_failure_count == 1 else "s") } @@ -907,9 +933,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + if !global_ansi_disabled { + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + } - when FANCY_OUTPUT { + if should_show_animations { if signals_were_raised { fmt.wprintln(batch_writer, ` Signals were raised during this test run. Log messages are likely to have collided with each other. diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin deleted file mode 100644 index 401804c71..000000000 --- a/core/testing/runner_windows.odin +++ /dev/null @@ -1,22 +0,0 @@ -#+private -package testing - -import win32 "core:sys/windows" - -console_ansi_init :: proc() { - stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) - if stdout != win32.INVALID_HANDLE && stdout != nil { - old_console_mode: u32 - if win32.GetConsoleMode(stdout, &old_console_mode) { - win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) - } - } - - stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) - if stderr != win32.INVALID_HANDLE && stderr != nil { - old_console_mode: u32 - if win32.GetConsoleMode(stderr, &old_console_mode) { - win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) - } - } -} diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 281fbde40..f9527e22f 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -12,9 +12,9 @@ package testing import "base:intrinsics" import "core:c/libc" -import "core:encoding/ansi" -import "core:sync" import "core:os" +import "core:sync" +import "core:terminal/ansi" @(private="file") stop_runner_flag: libc.sig_atomic_t @@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) { // NOTE(Feoramund): Using these write calls in a signal handler is // undefined behavior in C99 but possibly tolerated in POSIX 2008. // Either way, we may as well try to salvage what we can. - show_cursor := ansi.CSI + ansi.DECTCEM_SHOW - libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) - libc.fflush(libc.stdout) + if !global_ansi_disabled { + show_cursor := ansi.CSI + ansi.DECTCEM_SHOW + libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) + libc.fflush(libc.stdout) + } // This is an attempt at being compliant by avoiding printf. sigbuf: [8]byte diff --git a/examples/all/all_linux.odin b/examples/all/all_linux.odin index ca51d6562..dde712b8d 100644 --- a/examples/all/all_linux.odin +++ b/examples/all/all_linux.odin @@ -3,4 +3,8 @@ package all import linux "core:sys/linux" -_ :: linux \ No newline at end of file +import xlib "vendor:x11/xlib" + +_ :: linux + +_ :: xlib diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 0a17227b8..de037f6cd 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -58,7 +58,6 @@ import trace "core:debug/trace" import dynlib "core:dynlib" import net "core:net" -import ansi "core:encoding/ansi" import base32 "core:encoding/base32" import base64 "core:encoding/base64" import cbor "core:encoding/cbor" @@ -129,6 +128,9 @@ import strings "core:strings" import sync "core:sync" import testing "core:testing" +import terminal "core:terminal" +import ansi "core:terminal/ansi" + import edit "core:text/edit" import i18n "core:text/i18n" import match "core:text/match" @@ -201,7 +203,6 @@ _ :: pe _ :: trace _ :: dynlib _ :: net -_ :: ansi _ :: base32 _ :: base64 _ :: csv @@ -257,6 +258,8 @@ _ :: strconv _ :: strings _ :: sync _ :: testing +_ :: terminal +_ :: ansi _ :: scanner _ :: i18n _ :: match diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin index b224a3bbe..ebbfe786b 100644 --- a/examples/all/all_vendor.odin +++ b/examples/all/all_vendor.odin @@ -28,8 +28,6 @@ import nvg "vendor:nanovg" import nvg_gl "vendor:nanovg/gl" import fontstash "vendor:fontstash" -import xlib "vendor:x11/xlib" - _ :: cgltf // _ :: commonmark _ :: ENet @@ -57,8 +55,6 @@ _ :: nvg _ :: nvg_gl _ :: fontstash -_ :: xlib - // NOTE: needed for doc generator diff --git a/examples/all/all_vendor_windows.odin b/examples/all/all_vendor_windows.odin index 5087bac07..df6542cdd 100644 --- a/examples/all/all_vendor_windows.odin +++ b/examples/all/all_vendor_windows.odin @@ -3,8 +3,10 @@ package all import wgpu "vendor:wgpu" import b2 "vendor:box2d" import game_input "vendor:windows/GameInput" +import XAudio2 "vendor:windows/XAudio2" _ :: wgpu _ :: b2 _ :: game_input +_ :: XAudio2 diff --git a/misc/get-date.c b/misc/get-date.c index bf5b32738..b3eb1be78 100644 --- a/misc/get-date.c +++ b/misc/get-date.c @@ -9,5 +9,5 @@ int main(int arg_count, char const **arg_ptr) { time_t t = time(NULL); struct tm* now = localtime(&t); - printf("%04d%02d%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday); + printf("%04d-%02d-%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday); } \ No newline at end of file diff --git a/src/bug_report.cpp b/src/bug_report.cpp index 0a617fe39..32210c23e 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -667,8 +667,14 @@ gb_internal void print_bug_report_help() { gb_printf("-nightly"); #endif + String version = {}; + #ifdef GIT_SHA - gb_printf(":%s", GIT_SHA); + version.text = cast(u8 *)GIT_SHA; + version.len = gb_strlen(GIT_SHA); + if (version != "") { + gb_printf(":%.*s", LIT(version)); + } #endif gb_printf("\n"); diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c941e0f68..b3bbf726b 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -459,6 +459,7 @@ struct BuildContext { bool ignore_unknown_attributes; bool no_bounds_check; bool no_type_assert; + bool dynamic_literals; // Opt-in to `#+feature dynamic-literals` project-wide. bool no_output_files; bool no_crt; bool no_rpath; @@ -1915,12 +1916,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } - // TODO: Static map calls are bugged on `amd64sysv` abi. - if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) { - // ENFORCE DYNAMIC MAP CALLS - bc->dynamic_map_calls = true; - } - bc->ODIN_VALGRIND_SUPPORT = false; if (build_context.metrics.os != TargetOs_windows) { switch (bc->metrics.arch) { @@ -2214,11 +2209,34 @@ gb_internal bool init_build_paths(String init_filename) { while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) { output_name.len -= 1; } + // Only trim the extension if it's an Odin source file. + // This lets people build folders with extensions or files beginning with dots. + if (path_extension(output_name) == ".odin" && !path_is_directory(output_name)) { + output_name = remove_extension_from_path(output_name); + } output_name = remove_directory_from_path(output_name); - output_name = remove_extension_from_path(output_name); output_name = copy_string(ha, string_trim_whitespace(output_name)); - output_path = path_from_string(ha, output_name); - + // This is `path_from_string` without the extension trimming. + Path res = {}; + if (output_name.len > 0) { + String fullpath = path_to_full_path(ha, output_name); + defer (gb_free(ha, fullpath.text)); + + res.basename = directory_from_path(fullpath); + res.basename = copy_string(ha, res.basename); + + if (path_is_directory(fullpath)) { + if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') { + res.basename.len--; + } + } else { + isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len; + res.name = substring(fullpath, name_start, fullpath.len); + res.name = copy_string(ha, res.name); + } + } + output_path = res; + // Note(Dragos): This is a fix for empty filenames // Turn the trailing folder into the file name if (output_path.name.len == 0) { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 84e30d5b4..f384cb7e1 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -223,9 +223,9 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t data.kind = kind; data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl); - mutex_lock(&c->info->objc_types_mutex); + mutex_lock(&c->info->objc_objc_msgSend_mutex); map_set(&c->info->objc_msgSend_types, call, data); - mutex_unlock(&c->info->objc_types_mutex); + mutex_unlock(&c->info->objc_objc_msgSend_mutex); try_to_add_package_dependency(c, "runtime", "objc_msgSend"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret"); @@ -387,6 +387,59 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair"); return true; } break; + + case BuiltinProc_objc_ivar_get: + { + Type *self_type = nullptr; + + Operand self = {}; + check_expr_or_type(c, &self, ce->args[0]); + + if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } else if (!is_type_pointer(self.type)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + self_type = type_deref(self.type); + + if (!(self_type->kind == Type_Named && + self_type->Named.type_name != nullptr && + self_type->Named.type_name->TypeName.objc_class_name != "")) { + gbString t = type_to_string(self_type); + error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=) , got type %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar; + if (ivar_type == nullptr) { + gbString t = type_to_string(self_type); + error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=).", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) { + operand->type = type_hint; + } else { + operand->type = alloc_type_pointer(ivar_type); + } + + operand->mode = Addressing_Value; + return true; + + } break; } } @@ -2167,7 +2220,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_find_selector: case BuiltinProc_objc_find_class: case BuiltinProc_objc_register_selector: - case BuiltinProc_objc_register_class: + case BuiltinProc_objc_register_class: + case BuiltinProc_objc_ivar_get: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: @@ -3189,6 +3243,194 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_compress_values: { + Operand *ops = gb_alloc_array(temporary_allocator(), Operand, ce->args.count); + + isize value_count = 0; + + for_array(i, ce->args) { + Ast *arg = ce->args[i]; + Operand *op = &ops[i]; + check_multi_expr(c, op, arg); + if (op->mode == Addressing_Invalid) { + return false; + } + + if (op->type == nullptr || op->type == t_invalid) { + gbString s = expr_to_string(op->expr); + error(op->expr, "Invalid expression to '%.*s', got %s", LIT(builtin_name), s); + gb_string_free(s); + } + if (is_type_tuple(op->type)) { + value_count += op->type->Tuple.variables.count; + } else { + value_count += 1; + } + } + + GB_ASSERT(value_count >= 1); + + if (value_count == 1) { + *operand = ops[0]; + break; + } + + if (type_hint != nullptr) { + Type *th = base_type(type_hint); + if (th->kind == Type_Struct) { + if (value_count == th->Struct.fields.count) { + isize index = 0; + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + for (Entity *v : op->type->Tuple.variables) { + Operand x = {}; + x.mode = Addressing_Value; + x.type = v->type; + check_assignment(c, &x, th->Struct.fields[index++]->type, builtin_name); + if (x.mode == Addressing_Invalid) { + return false; + } + } + } else { + check_assignment(c, op, th->Struct.fields[index++]->type, builtin_name); + if (op->mode == Addressing_Invalid) { + return false; + } + } + } + + operand->type = type_hint; + operand->mode = Addressing_Value; + break; + } + } else if (is_type_array_like(th)) { + if (cast(i64)value_count == get_array_type_count(th)) { + Type *elem = base_array_type(th); + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + for (Entity *v : op->type->Tuple.variables) { + Operand x = {}; + x.mode = Addressing_Value; + x.type = v->type; + check_assignment(c, &x, elem, builtin_name); + if (x.mode == Addressing_Invalid) { + return false; + } + } + } else { + check_assignment(c, op, elem, builtin_name); + if (op->mode == Addressing_Invalid) { + return false; + } + } + } + + operand->type = type_hint; + operand->mode = Addressing_Value; + break; + } + } + } + + bool all_types_the_same = true; + Type *last_type = nullptr; + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + if (last_type == nullptr) { + op->type->Tuple.variables[0]->type; + } + for (Entity *v : op->type->Tuple.variables) { + if (!are_types_identical(last_type, v->type)) { + all_types_the_same = false; + break; + } + last_type = v->type; + } + } else { + if (last_type == nullptr) { + last_type = op->type; + } else { + if (!are_types_identical(last_type, op->type)) { + all_types_the_same = false; + break; + } + last_type = op->type; + } + } + } + + if (all_types_the_same) { + Type *elem_type = default_type(last_type); + if (is_type_untyped(elem_type)) { + gbString s = expr_to_string(call); + error(call, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name)); + gb_string_free(s); + return false; + } + + operand->type = alloc_type_array(elem_type, value_count); + operand->mode = Addressing_Value; + } else { + Type *st = alloc_type_struct_complete(); + st->Struct.fields = slice_make(permanent_allocator(), value_count); + st->Struct.tags = gb_alloc_array(permanent_allocator(), String, value_count); + st->Struct.offsets = gb_alloc_array(permanent_allocator(), i64, value_count); + + Scope *scope = create_scope(c->info, nullptr); + + Token token = {}; + token.kind = Token_Ident; + token.pos = ast_token(call).pos; + + isize index = 0; + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + for (Entity *v : op->type->Tuple.variables) { + Type *t = default_type(v->type); + if (is_type_untyped(t)) { + gbString s = expr_to_string(op->expr); + error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name)); + gb_string_free(s); + return false; + } + + gbString s = gb_string_make_reserve(permanent_allocator(), 32); + s = gb_string_append_fmt(s, "v%lld", cast(long long)index); + token.string = make_string_c(s); + Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved); + st->Struct.fields[index++] = e; + } + } else { + Type *t = default_type(op->type); + if (is_type_untyped(t)) { + gbString s = expr_to_string(op->expr); + error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name)); + gb_string_free(s); + return false; + } + + gbString s = gb_string_make_reserve(permanent_allocator(), 32); + s = gb_string_append_fmt(s, "v%lld", cast(long long)index); + token.string = make_string_c(s); + Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved); + st->Struct.fields[index++] = e; + } + } + + + gb_unused(type_size_of(st)); + + operand->type = st; + operand->mode = Addressing_Value; + } + break; + } + + case BuiltinProc_min: { // min :: proc($T: typeid) -> ordered // min :: proc(a: ..ordered) -> ordered @@ -5635,6 +5877,87 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } operand->mode = Addressing_Type; break; + case BuiltinProc_type_integer_to_unsigned: + if (operand->mode != Addressing_Type) { + error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + + if (is_type_polymorphic(operand->type)) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + { + Type *bt = base_type(operand->type); + + if (bt->kind != Type_Basic || + (bt->Basic.flags & BasicFlag_Unsigned) != 0 || + (bt->Basic.flags & BasicFlag_Integer) == 0) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Expected a signed integer type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + if ((bt->Basic.flags & BasicFlag_Untyped) != 0) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + Type *u_type = &basic_types[bt->Basic.kind + 1]; + + operand->type = u_type; + } + break; + case BuiltinProc_type_integer_to_signed: + if (operand->mode != Addressing_Type) { + error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + + if (is_type_polymorphic(operand->type)) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + { + Type *bt = base_type(operand->type); + + if (bt->kind != Type_Basic || + (bt->Basic.flags & BasicFlag_Unsigned) == 0 || + (bt->Basic.flags & BasicFlag_Integer) == 0) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Expected an unsigned integer type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + if ((bt->Basic.flags & BasicFlag_Untyped) != 0) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + if (bt->Basic.kind == Basic_uintptr) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Type %s does not have a signed integer mapping for '%.*s'", t, LIT(builtin_name)); + gb_string_free(t); + return false; + } + + Type *u_type = &basic_types[bt->Basic.kind - 1]; + + operand->type = u_type; + } + break; case BuiltinProc_type_merge: { operand->mode = Addressing_Type; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 2392775b1..d53c3c6b7 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -524,12 +524,90 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, if (decl != nullptr) { AttributeContext ac = {}; check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac); + if (e->kind == Entity_TypeName && ac.objc_class != "") { + e->TypeName.objc_class_name = ac.objc_class; + if (ac.objc_is_implementation) { + e->TypeName.objc_is_implementation = ac.objc_is_implementation; + e->TypeName.objc_superclass = ac.objc_superclass; + e->TypeName.objc_ivar = ac.objc_ivar; + e->TypeName.objc_context_provider = ac.objc_context_provider; + + mutex_lock(&ctx->info->objc_class_name_mutex); + bool class_exists = string_set_update(&ctx->info->obcj_class_name_set, ac.objc_class); + mutex_unlock(&ctx->info->objc_class_name_mutex); + if (class_exists) { + error(e->token, "@(objc_class) name '%.*s' has already been used elsewhere", LIT(ac.objc_class)); + } + + mpsc_enqueue(&ctx->info->objc_class_implementations, e); + + GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named); + + // Enqueue the contex_provider proc to be checked after it is resolved + if (e->TypeName.objc_context_provider != nullptr) { + mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e); + } + + // TODO(harold): I think there's a Check elsewhere in the checker for checking cycles. + // See about moving this to the right location. + // Ensure superclass hierarchy are all Objective-C classes and does not cycle + + // NOTE(harold): We check for superclass unconditionally (before checking if super is null) + // because this should be the case 99.99% of the time. Not subclassing something that + // is, or is the child of, NSObject means the objc runtime messaging will not properly work on this type. + TypeSet super_set{}; + type_set_init(&super_set, 8); + defer (type_set_destroy(&super_set)); + + type_set_update(&super_set, e->type); + + Type *super = ac.objc_superclass; + while (super != nullptr) { + if (type_set_update(&super_set, super)) { + error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered"); + break; + } + + check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); + + if (super->kind != Type_Named) { + error(e->token, "@(objc_superclass) Referenced type must be a named struct"); + break; + } + + Type* named_type = base_named_type(super); + GB_ASSERT(named_type->kind == Type_Named); + + if (!is_type_objc_object(named_type)) { + error(e->token, "@(objc_superclass) Superclass '%.*s' must be an Objective-C class", LIT(named_type->Named.name)); + break; + } + + if (named_type->Named.type_name->TypeName.objc_class_name == "") { + error(e->token, "@(objc_superclass) Superclass '%.*s' must have a valid @(objc_class) attribute", LIT(named_type->Named.name)); + break; + } + + super = named_type->Named.type_name->TypeName.objc_superclass; + } + } else { + if (ac.objc_superclass != nullptr) { + error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied"); + } else if (ac.objc_ivar != nullptr) { + error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied"); + } else if (ac.objc_context_provider != nullptr) { + error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied"); + } + } + if (type_size_of(e->type) > 0) { error(e->token, "@(objc_class) marked type must be of zero size"); } + } else if (ac.objc_is_implementation) { + error(e->token, "@(objc_implement) may only be applied when the @(objc_class) attribute is also applied"); } } @@ -922,7 +1000,7 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin } -gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) { +gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext &ac) { if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) { return; } @@ -934,48 +1012,107 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon error(e->token, "@(objc_name) is required with @(objc_type)"); } else { Type *t = ac.objc_type; - if (t->kind == Type_Named) { - Entity *tn = t->Named.type_name; - GB_ASSERT(tn->kind == Entity_TypeName); + GB_ASSERT(t->kind == Type_Named); // NOTE(harold): This is already checked for at the attribute resolution stage. + Entity *tn = t->Named.type_name; - if (tn->scope != e->scope) { - error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); - } else { - mutex_lock(&global_type_name_objc_metadata_mutex); - defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); + GB_ASSERT(tn->kind == Entity_TypeName); - if (!tn->TypeName.objc_metadata) { - tn->TypeName.objc_metadata = create_type_name_obj_c_metadata(); - } - auto *md = tn->TypeName.objc_metadata; - mutex_lock(md->mutex); - defer (mutex_unlock(md->mutex)); + if (tn->scope != e->scope) { + error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); + } else { - if (!ac.objc_is_class_method) { - bool ok = true; - for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { - if (entry.name == ac.objc_name) { - error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); - ok = false; - break; - } - } - if (ok) { - array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); - } + // Enable implementation by default if the class is an implementer too and + // @objc_implement was not set to false explicitly in this proc. + bool implement = tn->TypeName.objc_is_implementation; + if (ac.objc_is_disabled_implement) { + implement = false; + } + + if (implement) { + GB_ASSERT(e->kind == Entity_Procedure); + + auto &proc = e->type->Proc; + Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil; + + if (!tn->TypeName.objc_is_implementation) { + error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied"); + } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { + error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)"); + } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { + error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set"); + } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) { + error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention"); + } else if (proc.result_count > 1) { + error(e->token, "Objective-C method implementations may return at most 1 value"); } else { - bool ok = true; - for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { - if (entry.name == ac.objc_name) { - error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); - ok = false; - break; - } + // Always export unconditionally + // NOTE(harold): This means check_objc_methods() MUST be called before + // e->Procedure.is_export is set in check_proc_decl()! + if (ac.is_export) { + error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly"); } - if (ok) { - array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); + if (ac.link_name != "") { + error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly"); } + + ac.is_export = true; + ac.linkage = STR_LIT("strong"); + + auto method = ObjcMethodData{ ac, e }; + method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; + + CheckerInfo *info = ctx->info; + mutex_lock(&info->objc_method_mutex); + defer (mutex_unlock(&info->objc_method_mutex)); + + Array* method_list = map_get(&info->objc_method_implementations, t); + if (method_list) { + array_add(method_list, method); + } else { + auto list = array_make(permanent_allocator(), 1, 8); + list[0] = method; + + map_set(&info->objc_method_implementations, t, list); + } + } + } else if (ac.objc_selector != "") { + error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations."); + } + + mutex_lock(&global_type_name_objc_metadata_mutex); + defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); + + if (!tn->TypeName.objc_metadata) { + tn->TypeName.objc_metadata = create_type_name_obj_c_metadata(); + } + auto *md = tn->TypeName.objc_metadata; + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); + + if (!ac.objc_is_class_method) { + bool ok = true; + for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { + if (entry.name == ac.objc_name) { + error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); + ok = false; + break; + } + } + if (ok) { + array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); + } + } else { + bool ok = true; + for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { + if (entry.name == ac.objc_name) { + error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); + ok = false; + break; + } + } + if (ok) { + array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); } } } @@ -1145,6 +1282,9 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { break; } + // NOTE(harold): For Objective-C method implementations, this must happen after + // check_objc_methods() is called as it re-sets ac.is_export to true unconditionally. + // The same is true for the linkage, set below. e->Procedure.entry_point_only = ac.entry_point_only; e->Procedure.is_export = ac.is_export; @@ -1245,6 +1385,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } } + // NOTE(harold): See export/linkage note above(where is_export is assigned) regarding Objective-C method implementations bool is_foreign = e->Procedure.is_foreign; bool is_export = e->Procedure.is_export; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 10b37bbf3..95c898adf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -643,7 +643,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E gb_internal bool check_polymorphic_procedure_assignment(CheckerContext *c, Operand *operand, Type *type, Ast *poly_def_node, PolyProcData *poly_proc_data) { if (operand->expr == nullptr) return false; - Entity *base_entity = entity_of_node(operand->expr); + Entity *base_entity = entity_from_expr(operand->expr); if (base_entity == nullptr) return false; return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_def_node, poly_proc_data); } @@ -995,14 +995,34 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) { gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) { - i64 score = 0; - i64 distance = check_distance_between_types(c, operand, type, allow_array_programming); - bool ok = distance >= 0; - if (ok) { - score = assign_score_function(distance, is_variadic); + if (c == nullptr) { + GB_ASSERT(operand->mode == Addressing_Value); + GB_ASSERT(is_type_typed(operand->type)); } - if (score_) *score_ = score; - return ok; + if (operand->mode == Addressing_Invalid || type == t_invalid) { + if (score_) *score_ = 0; + return false; + } + + // Handle polymorphic procedure used as default parameter + if (operand->mode == Addressing_Value && is_type_proc(type) && is_type_proc(operand->type)) { + Entity *e = entity_from_expr(operand->expr); + if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type) && !is_type_polymorphic(type)) { + // Special case: Allow a polymorphic procedure to be used as default value for concrete proc type + // during the initial check. It will be properly instantiated when actually used. + if (score_) *score_ = assign_score_function(1); + return true; + } + } + + i64 score = check_distance_between_types(c, operand, type, allow_array_programming); + if (score >= 0) { + if (score_) *score_ = assign_score_function(score, is_variadic); + return true; + } + + if (score_) *score_ = 0; + return false; } @@ -5441,8 +5461,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod } } + if (operand->type && is_type_simd_vector(type_deref(operand->type))) { + String field_name = selector->Ident.token.string; + if (field_name.len == 1) { + error(op_expr, "Extracting an element from a #simd array using .%.*s syntax is disallowed, prefer `simd.extract`", LIT(field_name)); + } else { + error(op_expr, "Extracting elements from a #simd array using .%.*s syntax is disallowed, prefer `swizzle`", LIT(field_name)); + } + return nullptr; + } + if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr && - (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { + (is_type_array(type_deref(operand->type)))) { String field_name = selector->Ident.token.string; if (1 < field_name.len && field_name.len <= 4) { u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'}; @@ -5497,7 +5527,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Type *original_type = operand->type; Type *array_type = base_type(type_deref(original_type)); - GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector); + GB_ASSERT(array_type->kind == Type_Array); i64 array_count = get_array_type_count(array_type); @@ -5538,10 +5568,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod break; } - if (array_type->kind == Type_SimdVector) { - operand->mode = Addressing_Value; - } - Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved); add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value); return swizzle_entity; @@ -9413,7 +9439,7 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) { } gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) { - if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0) { + if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0 && !build_context.dynamic_literals) { ERROR_BLOCK(); error(node, "Compound literals of dynamic types are disabled by default"); error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n"); @@ -10996,7 +11022,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast return kind; case_end; - case_ast_node(i, Implicit, node) + case_ast_node(i, Implicit, node); switch (i->kind) { case Token_context: { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 1b44ff4d7..0460f5bec 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2108,10 +2108,12 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f if (init_type == nullptr) { init_type = t_invalid; } else if (is_type_polymorphic(base_type(init_type))) { + /* DISABLED: This error seems too aggressive for instantiated generic types. gbString str = type_to_string(init_type); error(vd->type, "Invalid use of a polymorphic type '%s' in variable declaration", str); gb_string_free(str); init_type = t_invalid; + */ } if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) { Entity *e = entities[0]; diff --git a/src/check_type.cpp b/src/check_type.cpp index 452da4023..431698459 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1910,9 +1910,18 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para case ParameterValue_Location: case ParameterValue_Expression: case ParameterValue_Value: + // Special case for polymorphic procedures as default values + if (param_value.ast_value != nullptr) { + Entity *e = entity_from_expr(param_value.ast_value); + if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type)) { + // Allow polymorphic procedures as default parameter values + // The type will be correctly determined at call site + break; + } + } gbString str = type_to_string(type); error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str); - gb_string_free(str); + gb_string_free(str); break; } } diff --git a/src/checker.cpp b/src/checker.cpp index 5e1517875..aaa815365 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -728,12 +728,17 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl bool is_unused = false; if (vet_unused && check_vet_unused(c, e, &ve_unused)) { is_unused = true; - } else if (vet_unused_procedures && - e->kind == Entity_Procedure) { + } else if (vet_unused_procedures && e->kind == Entity_Procedure) { if (e->flags&EntityFlag_Used) { is_unused = false; } else if (e->flags & EntityFlag_Require) { is_unused = false; + } else if (e->flags & EntityFlag_Init) { + is_unused = false; + } else if (e->flags & EntityFlag_Fini) { + is_unused = false; + } else if (e->Procedure.is_export) { + is_unused = false; } else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") { is_unused = false; } else { @@ -1351,10 +1356,12 @@ gb_internal void init_universal(void) { t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete()); t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete()); t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete()); + t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete()); t_objc_id = alloc_type_pointer(t_objc_object); t_objc_SEL = alloc_type_pointer(t_objc_selector); t_objc_Class = alloc_type_pointer(t_objc_class); + t_objc_Ivar = alloc_type_pointer(t_objc_ivar); } } @@ -1387,6 +1394,10 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->defineables, a); map_init(&i->objc_msgSend_types); + mpsc_init(&i->objc_class_implementations, a); + string_set_init(&i->obcj_class_name_set, 0); + map_init(&i->objc_method_implementations); + string_map_init(&i->load_file_cache); array_init(&i->all_procedures, heap_allocator()); @@ -1497,6 +1508,8 @@ gb_internal void init_checker(Checker *c) { TIME_SECTION("init proc queues"); mpsc_init(&c->procs_with_deferred_to_check, a); //, 1<<10); + mpsc_init(&c->procs_with_objc_context_provider_to_check, a); + // NOTE(bill): 1 Mi elements should be enough on average array_init(&c->procs_to_check, heap_allocator(), 0, 1<<20); @@ -3662,6 +3675,33 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } } return true; + } else if (name == "objc_implement") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_implementation = ev.value_bool; + + if (!ac->objc_is_implementation) { + ac->objc_is_disabled_implement = true; + } + } else if (ev.kind == ExactValue_Invalid) { + ac->objc_is_implementation = true; + } else { + error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); + } + + return true; + } else if (name == "objc_selector") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + if (string_is_valid_identifier(ev.value_string)) { + ac->objc_selector = ev.value_string; + } else { + error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "require_target_feature") { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { @@ -3907,6 +3947,51 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) { ac->objc_class = ev.value_string; } return true; + } else if (name == "objc_implement") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_implementation = ev.value_bool; + } else if (ev.kind == ExactValue_Invalid) { + ac->objc_is_implementation = true; + } else { + error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_superclass") { + Type *objc_superclass = check_type(c, value); + + if (objc_superclass != nullptr) { + ac->objc_superclass = objc_superclass; + } else { + error(value, "'%.*s' expected a named type", LIT(name)); + } + return true; + } else if (name == "objc_ivar") { + Type *objc_ivar = check_type(c, value); + + if (objc_ivar != nullptr && objc_ivar->kind == Type_Named) { + ac->objc_ivar = objc_ivar; + } else { + error(value, "'%.*s' expected a named type", LIT(name)); + } + return true; + } else if (name == "objc_context_provider") { + Operand o = {}; + check_expr(c, &o, value); + Entity *e = entity_of_node(o.expr); + + if (e != nullptr) { + if (ac->objc_context_provider != nullptr) { + error(elem, "Previous usage of a 'objc_context_provider' attribute"); + } + if (e->kind != Entity_Procedure) { + error(elem, "'objc_context_provider' must refer to a procedure"); + } else { + ac->objc_context_provider = e; + } + + return true; + } } return false; } @@ -6240,6 +6325,12 @@ gb_internal void check_deferred_procedures(Checker *c) { continue; } + if (dst->flags & EntityFlag_Disabled) { + // Prevent procedures that have been disabled from acting as deferrals. + src->Procedure.deferred_procedure = {}; + continue; + } + GB_ASSERT(is_type_proc(src->type)); GB_ASSERT(is_type_proc(dst->type)); Type *src_params = base_type(src->type)->Proc.params; @@ -6395,6 +6486,44 @@ gb_internal void check_deferred_procedures(Checker *c) { } +gb_internal void check_objc_context_provider_procedures(Checker *c) { + for (Entity *e = nullptr; mpsc_dequeue(&c->procs_with_objc_context_provider_to_check, &e); /**/) { + GB_ASSERT(e->kind == Entity_TypeName); + + Entity *proc_entity = e->TypeName.objc_context_provider; + GB_ASSERT(proc_entity->kind == Entity_Procedure); + + auto &proc = proc_entity->type->Proc; + + Type *return_type = proc.result_count != 1 ? t_untyped_nil : base_named_type(proc.results->Tuple.variables[0]->type); + if (return_type != t_context) { + error(proc_entity->token, "The @(objc_context_provider) procedure must only return a context."); + } + + const char *self_param_err = "The @(objc_context_provider) procedure must take as a parameter a single pointer to the @(objc_type) value."; + if (proc.param_count != 1) { + error(proc_entity->token, self_param_err); + } + + Type *self_param = base_type(proc.params->Tuple.variables[0]->type); + if (self_param->kind != Type_Pointer) { + error(proc_entity->token, self_param_err); + } + + Type *self_type = base_named_type(self_param->Pointer.elem); + if (!internal_check_is_assignable_to(self_type, e->type) && + !(e->TypeName.objc_ivar && internal_check_is_assignable_to(self_type, e->TypeName.objc_ivar))) { + error(proc_entity->token, self_param_err); + } + if (proc.calling_convention != ProcCC_CDecl && proc.calling_convention != ProcCC_Contextless) { + error(e->token, self_param_err); + } + if (proc.is_polymorphic) { + error(e->token, self_param_err); + } + } +} + gb_internal void check_unique_package_names(Checker *c) { ERROR_BLOCK(); @@ -6555,6 +6684,7 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { } } + gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("map full filepaths to scope"); add_type_info_type(&c->builtin_ctx, t_invalid); @@ -6664,6 +6794,9 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check deferred procedures"); check_deferred_procedures(c); + TIME_SECTION("check objc context provider procedures"); + check_objc_context_provider_procedures(c); + TIME_SECTION("calculate global init order"); calculate_global_init_order(c); diff --git a/src/checker.hpp b/src/checker.hpp index dd82d9bdd..0cdfd69ab 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -149,8 +149,14 @@ struct AttributeContext { String objc_class; String objc_name; - bool objc_is_class_method; + String objc_selector; Type * objc_type; + Type * objc_superclass; + Type * objc_ivar; + Entity *objc_context_provider; + bool objc_is_class_method; + bool objc_is_implementation; // This struct or proc provides a class/method implementation, not a binding to an existing type. + bool objc_is_disabled_implement; // This means the method explicitly set @objc_implement to false so it won't be inferred from the class' attribute. String require_target_feature; // required by the target micro-architecture String enable_target_feature; // will be enabled for the procedure only @@ -366,6 +372,11 @@ struct ObjcMsgData { Type *proc_type; }; +struct ObjcMethodData { + AttributeContext ac; + Entity *proc_entity; +}; + enum LoadFileTier { LoadFileTier_Invalid, LoadFileTier_Exists, @@ -477,9 +488,17 @@ struct CheckerInfo { MPSCQueue intrinsics_entry_point_usage; - BlockingMutex objc_types_mutex; + BlockingMutex objc_objc_msgSend_mutex; PtrMap objc_msgSend_types; + BlockingMutex objc_class_name_mutex; + StringSet obcj_class_name_set; + MPSCQueue objc_class_implementations; + + BlockingMutex objc_method_mutex; + PtrMap> objc_method_implementations; + + BlockingMutex load_file_mutex; StringMap load_file_cache; @@ -556,6 +575,7 @@ struct Checker { CheckerContext builtin_ctx; MPSCQueue procs_with_deferred_to_check; + MPSCQueue procs_with_objc_context_provider_to_check; Array procs_to_check; BlockingMutex nested_proc_lits_mutex; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 4c4387923..f3b55daa4 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -26,6 +26,7 @@ enum BuiltinProcId { BuiltinProc_conj, BuiltinProc_expand_values, + BuiltinProc_compress_values, BuiltinProc_min, BuiltinProc_max, @@ -234,6 +235,9 @@ BuiltinProc__type_begin, BuiltinProc_type_convert_variants_to_pointers, BuiltinProc_type_merge, + BuiltinProc_type_integer_to_unsigned, + BuiltinProc_type_integer_to_signed, + BuiltinProc__type_simple_boolean_begin, BuiltinProc_type_is_boolean, BuiltinProc_type_is_integer, @@ -338,6 +342,7 @@ BuiltinProc__type_end, BuiltinProc_objc_find_class, BuiltinProc_objc_register_selector, BuiltinProc_objc_register_class, + BuiltinProc_objc_ivar_get, BuiltinProc_constant_utf16_cstring, @@ -375,6 +380,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("conj"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("expand_values"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, + {STR_LIT("compress_values"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("min"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("max"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, @@ -582,6 +588,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_convert_variants_to_pointers"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_merge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_integer_to_unsigned"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_integer_to_signed"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_boolean"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_integer"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -686,6 +695,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/entity.cpp b/src/entity.cpp index 9946a3a5f..a16779419 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -235,6 +235,10 @@ struct Entity { Type * type_parameter_specialization; String ir_mangled_name; bool is_type_alias; + bool objc_is_implementation; + Type* objc_superclass; + Type* objc_ivar; + Entity*objc_context_provider; String objc_class_name; TypeNameObjCMetadata *objc_metadata; } TypeName; diff --git a/src/linker.cpp b/src/linker.cpp index ec165ef7d..41d4a13a1 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -769,7 +769,17 @@ try_cross_linking:; gbString platform_lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(platform_lib_str)); if (build_context.metrics.os == TargetOs_darwin) { - platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib "); + // Get the MacOSX SDK path. + gbString darwin_sdk_path = gb_string_make(temporary_allocator(), ""); + if (!system_exec_command_line_app_output("xcrun --sdk macosx --show-sdk-path", &darwin_sdk_path)) { + darwin_sdk_path = gb_string_set(darwin_sdk_path, "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"); + } else { + // Trim the trailing newline. + darwin_sdk_path = gb_string_trim_space(darwin_sdk_path); + } + platform_lib_str = gb_string_append_fmt(platform_lib_str, "--sysroot %s ", darwin_sdk_path); + + platform_lib_str = gb_string_appendc(platform_lib_str, "-L/usr/local/lib "); // Homebrew's default library path, checking if it exists to avoid linking warnings. if (gb_file_exists("/opt/homebrew/lib")) { @@ -791,6 +801,9 @@ try_cross_linking:; // This points the linker to where the entry point is link_settings = gb_string_appendc(link_settings, "-e _main "); } + } else if (build_context.metrics.os == TargetOs_freebsd) { + // FreeBSD pkg installs third-party shared libraries in /usr/local/lib. + platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib "); } else if (build_context.metrics.os == TargetOs_openbsd) { // OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread. platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib "); diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index c8e1ca764..af08722c3 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -977,7 +977,7 @@ namespace lbAbiAmd64SysV { return types[0]; } - return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, sz == 0); + return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, false); } gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off) { @@ -1231,38 +1231,24 @@ namespace lbAbiArm64 { } } else { i64 size = lb_sizeof(return_type); - if (size <= 16) { - LLVMTypeRef cast_type = nullptr; - - if (size == 0) { - cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); - } else if (size <= 8) { - cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); - } else { - unsigned count = cast(unsigned)((size+7)/8); - - LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); - LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count); - - i64 size_copy = size; - for (unsigned i = 0; i < count; i++) { - if (size_copy >= 8) { - types[i] = llvm_i64; - } else { - types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy); - } - size_copy -= 8; - } - GB_ASSERT(size_copy <= 0); - cast_type = LLVMStructTypeInContext(c, types, count, true); - } - return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr); - } else { + if (size > 16) { LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); } + + GB_ASSERT(size <= 16); + LLVMTypeRef cast_type = nullptr; + if (size == 0) { + cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); + } else if (size <= 8) { + cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); + } else { + LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); + cast_type = llvm_array_type(llvm_i64, 2); + } + return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr); } } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b69cbc3a5..c73552d57 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1173,6 +1173,344 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) { return p; } +String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { + // NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 + + // NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always? + #define INT_SIZE_ENCODING (build_context.metrics.int_size == 4 ? "i" : "q") + switch (t->kind) { + case Type_Basic: { + switch (t->Basic.kind) { + case Basic_Invalid: + return str_lit("?"); + + case Basic_llvm_bool: + case Basic_bool: + case Basic_b8: + return str_lit("B"); + + case Basic_b16: + return str_lit("C"); + case Basic_b32: + return str_lit("I"); + case Basic_b64: + return str_lit("q"); + case Basic_i8: + return str_lit("c"); + case Basic_u8: + return str_lit("C"); + case Basic_i16: + case Basic_i16le: + case Basic_i16be: + return str_lit("s"); + case Basic_u16: + case Basic_u16le: + case Basic_u16be: + return str_lit("S"); + case Basic_i32: + case Basic_i32le: + case Basic_i32be: + return str_lit("i"); + case Basic_u32le: + case Basic_u32: + case Basic_u32be: + return str_lit("I"); + case Basic_i64: + case Basic_i64le: + case Basic_i64be: + return str_lit("q"); + case Basic_u64: + case Basic_u64le: + case Basic_u64be: + return str_lit("Q"); + case Basic_i128: + case Basic_i128le: + case Basic_i128be: + return str_lit("t"); + case Basic_u128: + case Basic_u128le: + case Basic_u128be: + return str_lit("T"); + case Basic_rune: + return str_lit("I"); + case Basic_f16: + case Basic_f16le: + case Basic_f16be: + return str_lit("s"); // @harold: Closest we've got? + case Basic_f32: + case Basic_f32le: + case Basic_f32be: + return str_lit("f"); + case Basic_f64: + case Basic_f64le: + case Basic_f64be: + return str_lit("d"); + + case Basic_complex32: return str_lit("{complex32=ss}"); // No f16 encoding, so fallback to i16, as above in Basic_f16* + case Basic_complex64: return str_lit("{complex64=ff}"); + case Basic_complex128: return str_lit("{complex128=dd}"); + case Basic_quaternion64: return str_lit("{quaternion64=ssss}"); + case Basic_quaternion128: return str_lit("{quaternion128=ffff}"); + case Basic_quaternion256: return str_lit("{quaternion256=dddd}"); + + case Basic_int: + return str_lit(INT_SIZE_ENCODING); + case Basic_uint: + return build_context.metrics.int_size == 4 ? str_lit("I") : str_lit("Q"); + case Basic_uintptr: + case Basic_rawptr: + return str_lit("^v"); + + case Basic_string: + return build_context.metrics.int_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}"); + + case Basic_cstring: return str_lit("*"); + case Basic_any: return str_lit("{any=^v^v}"); // rawptr + ^Type_Info + + case Basic_typeid: + GB_ASSERT(t->Basic.size == 8); + return str_lit("q"); + + // Untyped types + case Basic_UntypedBool: + case Basic_UntypedInteger: + case Basic_UntypedFloat: + case Basic_UntypedComplex: + case Basic_UntypedQuaternion: + case Basic_UntypedString: + case Basic_UntypedRune: + case Basic_UntypedNil: + case Basic_UntypedUninit: + GB_PANIC("Untyped types cannot be @encoded()"); + return str_lit("?"); + } + break; + } + + case Type_Named: + case Type_Struct: + case Type_Union: { + Type* base = t; + if (base->kind == Type_Named) { + base = base_type(base); + if(base->kind != Type_Struct && base->kind != Type_Union) { + return lb_get_objc_type_encoding(base, pointer_depth); + } + } + + const bool is_union = base->kind == Type_Union; + if (!is_union) { + // Check for objc_SEL + if (internal_check_is_assignable_to(base, t_objc_SEL)) { + return str_lit(":"); + } + + // Check for objc_Class + if (internal_check_is_assignable_to(base, t_objc_SEL)) { + return str_lit("#"); + } + + // Treat struct as an Objective-C Class? + if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) { + return str_lit("#"); + } + } + + if (is_type_objc_object(base)) { + return str_lit("@"); + } + + + gbString s = gb_string_make_reserve(temporary_allocator(), 16); + s = gb_string_append_length(s, is_union ? "(" :"{", 1); + if (t->kind == Type_Named) { + s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len); + } + + // Write fields + if (pointer_depth < 2) { + s = gb_string_append_length(s, "=", 1); + + if (!is_union) { + for( auto& f : base->Struct.fields ) { + String field_type = lb_get_objc_type_encoding(f->type, pointer_depth); + s = gb_string_append_length(s, field_type.text, field_type.len); + } + } else { + for( auto& v : base->Union.variants ) { + String variant_type = lb_get_objc_type_encoding(v, pointer_depth); + s = gb_string_append_length(s, variant_type.text, variant_type.len); + } + } + } + + s = gb_string_append_length(s, is_union ? ")" :"}", 1); + + return make_string_c(s); + } + + case Type_Generic: + GB_PANIC("Generic types cannot be @encoded()"); + return str_lit("?"); + + case Type_Pointer: { + String pointee = lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1); + // Special case for Objective-C Objects + if (pointer_depth == 0 && pointee == "@") { + return pointee; + } + + return concatenate_strings(temporary_allocator(), str_lit("^"), pointee); + } + + case Type_MultiPointer: + return concatenate_strings(temporary_allocator(), str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1)); + + case Type_Array: { + String type_str = lb_get_objc_type_encoding(t->Array.elem, pointer_depth); + + gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8); + s = gb_string_append_fmt(s, "[%lld%.*s]", t->Array.count, LIT(type_str)); + return make_string_c(s); + } + + case Type_EnumeratedArray: { + String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, pointer_depth); + + gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8); + s = gb_string_append_fmt(s, "[%lld%.*s]", t->EnumeratedArray.count, LIT(type_str)); + return make_string_c(s); + } + + case Type_Slice: { + String type_str = lb_get_objc_type_encoding(t->Slice.elem, pointer_depth); + gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8); + s = gb_string_append_fmt(s, "{slice=^%.*s%s}", LIT(type_str), INT_SIZE_ENCODING); + return make_string_c(s); + } + + case Type_DynamicArray: { + String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, pointer_depth); + gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8); + s = gb_string_append_fmt(s, "{dynamic=^%.*s%s%sAllocator={?^v}}", LIT(type_str), INT_SIZE_ENCODING, INT_SIZE_ENCODING); + return make_string_c(s); + } + + case Type_Map: + return str_lit("{^v^v{Allocator=?^v}}"); + case Type_Enum: + return lb_get_objc_type_encoding(t->Enum.base_type, pointer_depth); + case Type_Tuple: + // NOTE(harold): Is this type allowed here? + return str_lit("?"); + case Type_Proc: + return str_lit("?"); + case Type_BitSet: + return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth); + + case Type_SimdVector: { + String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth); + gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5); + gb_string_append_fmt(s, "[%lld%.*s]", t->SimdVector.count, LIT(type_str)); + return make_string_c(s); + } + + case Type_Matrix: { + String type_str = lb_get_objc_type_encoding(t->Matrix.elem, pointer_depth); + gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5); + i64 element_count = t->Matrix.column_count * t->Matrix.row_count; + gb_string_append_fmt(s, "[%lld%.*s]", element_count, LIT(type_str)); + return make_string_c(s); + } + + case Type_BitField: + return lb_get_objc_type_encoding(t->BitField.backing_type, pointer_depth); + case Type_SoaPointer: { + gbString s = gb_string_make_reserve(temporary_allocator(), 8); + s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING); + return make_string_c(s); + } + + } // End switch t->kind + #undef INT_SIZE_ENCODING + + GB_PANIC("Unreachable"); + return str_lit(""); +} + +struct lbObjCGlobalClass { + lbObjCGlobal g; + lbValue class_value; // Local registered class value +}; + +gb_internal void lb_register_objc_thing( + StringSet &handled, + lbModule *m, + Array &args, + Array &class_impls, + StringMap &class_map, + lbProcedure *p, + lbObjCGlobal const &g, + char const *call +) { + if (string_set_update(&handled, g.name)) { + return; + } + + lbAddr addr = {}; + lbValue *found = string_map_get(&m->members, g.global_name); + if (found) { + addr = lb_addr(*found); + } else { + lbValue v = {}; + LLVMTypeRef t = lb_type(m, g.type); + v.value = LLVMAddGlobal(m->mod, t, g.global_name); + v.type = alloc_type_pointer(g.type); + addr = lb_addr(v); + LLVMSetInitializer(v.value, LLVMConstNull(t)); + } + + lbValue class_ptr = {}; + lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); + + // If this class requires an implementation, save it for registration below. + if (g.class_impl_type != nullptr) { + + // Make sure the superclass has been initialized before us + lbValue superclass_value = lb_const_nil(m, t_objc_Class); + + auto &tn = g.class_impl_type->Named.type_name->TypeName; + Type *superclass = tn.objc_superclass; + if (superclass != nullptr) { + auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name); + lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call); + GB_ASSERT(superclass_global.class_value.value); + + superclass_value = superclass_global.class_value; + } + + args.count = 3; + args[0] = superclass_value; + args[1] = class_name; + args[2] = lb_const_int(m, t_uint, 0); + class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + + array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); + } + else { + args.count = 1; + args[0] = class_name; + class_ptr = lb_emit_runtime_call(p, call, args); + } + + lb_addr_store(p, addr, class_ptr); + + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_value = class_ptr; + } +} + gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { if (p == nullptr) { return; @@ -1186,38 +1524,329 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { string_set_init(&handled); defer (string_set_destroy(&handled)); - auto args = array_make(temporary_allocator(), 1); + auto args = array_make(temporary_allocator(), 3, 8); + auto class_impls = array_make(temporary_allocator(), 0, 16); + + // Register all class implementations unconditionally, even if not statically referenced + for (Entity *e = {}; mpsc_dequeue(&gen->info->objc_class_implementations, &e); /**/) { + GB_ASSERT(e->kind == Entity_TypeName && e->TypeName.objc_is_implementation); + lb_handle_objc_find_or_register_class(p, e->TypeName.objc_class_name, e->type); + } + + // Ensure classes that have been implicitly referenced through + // the objc_superclass attribute have a global variable available for them. + TypeSet class_set{}; + type_set_init(&class_set, gen->objc_classes.count+16); + defer (type_set_destroy(&class_set)); + + auto referenced_classes = array_make(temporary_allocator()); + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + array_add(&referenced_classes, g); + + Type *cls = g.class_impl_type; + while (cls) { + if (type_set_update(&class_set, cls)) { + break; + } + GB_ASSERT(cls->kind == Type_Named); + + cls = cls->Named.type_name->TypeName.objc_superclass; + } + } + + for (auto pair : class_set) { + auto& tn = pair.type->Named.type_name->TypeName; + Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type; + lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl); + } + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + array_add( &referenced_classes, g ); + } + + // Add all class globals to a map so that we can look them up dynamically + // in order to resolve out-of-order because classes that are being implemented + // require their superclasses to be registered before them. + StringMap global_class_map{}; + string_map_init(&global_class_map, (usize)gen->objc_classes.count); + defer (string_map_destroy(&global_class_map)); + + for (lbObjCGlobal g :referenced_classes) { + string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); + } LLVMSetLinkage(p->value, LLVMInternalLinkage); lb_begin_procedure_body(p); - auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) { - if (!string_set_update(&handled, g.name)) { - lbAddr addr = {}; - lbValue *found = string_map_get(&m->members, g.global_name); - if (found) { - addr = lb_addr(*found); - } else { - lbValue v = {}; - LLVMTypeRef t = lb_type(m, g.type); - v.value = LLVMAddGlobal(m->mod, t, g.global_name); - v.type = alloc_type_pointer(g.type); - addr = lb_addr(v); - LLVMSetInitializer(v.value, LLVMConstNull(t)); - } - - args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name)); - lbValue ptr = lb_emit_runtime_call(p, call, args); - lb_addr_store(p, addr, ptr); - } - }; - - for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { - register_thing(p, g, "objc_lookUpClass"); + // Register class globals, gathering classes that must be implemented + for (auto& kv : global_class_map) { + lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass"); } + // Prefetch selectors for implemented methods so that they can also be registered. + for (const auto& cd : class_impls) { + auto& g = cd.g; + Type *class_type = g.class_impl_type; + + Array* methods = map_get(&m->info->objc_method_implementations, class_type); + if (!methods) { + continue; + } + + for (const ObjcMethodData& md : *methods) { + lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector); + } + } + + // Now we can register all referenced selectors for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { - register_thing(p, g, "sel_registerName"); + lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName"); + } + + + // Emit method wrapper implementations and registration + auto wrapper_args = array_make(temporary_allocator(), 2, 8); + auto get_context_args = array_make(temporary_allocator(), 1); + + + PtrMap ivar_map{}; + map_init(&ivar_map, gen->objc_ivars.count); + + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_ivars, &g); /**/) { + map_set(&ivar_map, g.class_impl_type, g); + } + + for (const auto &cd : class_impls) { + auto &g = cd.g; + Type *class_type = g.class_impl_type; + Type *class_ptr_type = alloc_type_pointer(class_type); + lbValue class_value = cd.class_value; + + Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; + + Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider; + Type *contex_provider_self_ptr_type = nullptr; + Type *contex_provider_self_named_type = nullptr; + bool is_context_provider_ivar = false; + lbValue context_provider_proc_value{}; + + if (context_provider) { + context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider); + + contex_provider_self_ptr_type = base_type(context_provider->type->Proc.params->Tuple.variables[0]->type); + GB_ASSERT(contex_provider_self_ptr_type->kind == Type_Pointer); + contex_provider_self_named_type = base_named_type(type_deref(contex_provider_self_ptr_type)); + + is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type); + } + + + Array *methods = map_get(&m->info->objc_method_implementations, class_type); + if (!methods) { + continue; + } + + for (const ObjcMethodData &md : *methods) { + GB_ASSERT( md.proc_entity->kind == Entity_Procedure); + Type *method_type = md.proc_entity->type; + + String proc_name = make_string_c("__$objc_method::"); + proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name); + proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::")); + proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name); + + wrapper_args.count = 2; + wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type; + wrapper_args[1] = t_objc_SEL; + + isize method_param_count = method_type->Proc.param_count; + isize method_param_offset = 0; + + if (!md.ac.objc_is_class_method) { + GB_ASSERT(method_param_count >= 1); + method_param_count -= 1; + method_param_offset = 1; + } + + for (isize i = 0; i < method_param_count; i++) { + array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type); + } + + Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true); + Type *wrapper_results_tuple = nullptr; + + if (method_type->Proc.result_count > 0) { + GB_ASSERT(method_type->Proc.result_count == 1); + wrapper_results_tuple = alloc_type_tuple_from_field_types(&method_type->Proc.results->Tuple.variables[0]->type, 1, false, true); + } + + Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, wrapper_args_tuple->Tuple.variables.count, + wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl); + + lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type); + lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + + // Emit the wrapper + LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); + lb_begin_procedure_body(wrapper_proc); + { + if (method_type->Proc.calling_convention == ProcCC_Odin) { + GB_ASSERT(context_provider); + + // Emit the get odin context call + + get_context_args[0] = lbValue { + wrapper_proc->raw_input_parameters[0], + contex_provider_self_ptr_type, + }; + + if (is_context_provider_ivar) { + // The context provider takes the ivar's type. + // Emit an objc_ivar_get call and use that pointer for 'self' instead. + lbValue real_self { + wrapper_proc->raw_input_parameters[0], + class_ptr_type + }; + get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self); + } + + lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); + lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context)); + lb_push_context_onto_stack(wrapper_proc, context_addr); + } + + + auto method_call_args = array_make(temporary_allocator(), method_param_count + method_param_offset); + + if (!md.ac.objc_is_class_method) { + method_call_args[0] = lbValue { + wrapper_proc->raw_input_parameters[0], + class_ptr_type, + }; + } + + for (isize i = 0; i < method_param_count; i++) { + method_call_args[i+method_param_offset] = lbValue { + wrapper_proc->raw_input_parameters[i+2], + method_type->Proc.params->Tuple.variables[i+method_param_offset]->type, + }; + } + lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); + + // Call real procedure for method from here, passing the parameters expected, if any. + lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args); + + if (wrapper_results_tuple != nullptr) { + auto &result_var = method_type->Proc.results->Tuple.variables[0]; + return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type); + lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos); + } + } + lb_end_procedure_body(wrapper_proc); + + + // Add the method to the class + String method_encoding = str_lit("v"); + // TODO (harold): Checker must ensure that objc_methods have a single return value or none! + GB_ASSERT(method_type->Proc.result_count <= 1); + if (method_type->Proc.result_count != 0) { + method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type); + } + + if (!md.ac.objc_is_class_method) { + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:")); + } else { + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); + } + + for (isize i = method_param_offset; i < method_param_count; i++) { + Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; + String param_encoding = lb_get_objc_type_encoding(param_type); + + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); + } + + // Emit method registration + lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector); + GB_ASSERT(sel_address); + lbValue selector_value = lb_addr_load(p, *sel_address); + + args.count = 4; + args[0] = class_value; // Class + args[1] = selector_value; // SEL + args[2] = lbValue { wrapper_proc->value, wrapper_proc->type }; + args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding)); + + // TODO(harold): Emit check BOOL result and panic if false. + lb_emit_runtime_call(p, "class_addMethod", args); + + } // End methods + + // Add ivar if we have one + if (ivar_type != nullptr) { + // Register a single ivar for this class + Type *ivar_base = ivar_type->Named.base; + + // @note(harold): The alignment is supposed to be passed as log2(alignment): https://developer.apple.com/documentation/objectivec/class_addivar(_:_:_:_:_:)?language=objc + const i64 size = type_size_of(ivar_base); + const i64 alignment = (i64)floor_log2((u64)type_align_of(ivar_base)); + + // NOTE(harold): I've opted to not emit the type encoding for ivars in order to keep the data private. + // If there is desire in the future to emit the type encoding for introspection through the Obj-C runtime, + // then perhaps an option can be added for it then. + // Should we pass the actual type encoding? Might not be ideal for obfuscation. + String ivar_name = str_lit("__$ivar"); + String ivar_types = str_lit("{= }"); //lb_get_objc_type_encoding(ivar_type); + args.count = 5; + args[0] = class_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name)); + args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size)); + args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment)); + args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types)); + lb_emit_runtime_call(p, "class_addIvar", args); + } + + // Complete the class registration + args.count = 1; + args[0] = class_value; + lb_emit_runtime_call(p, "objc_registerClassPair", args); + } + + // Register ivar offsets for any `objc_ivar_get` expressions emitted. + for (auto const& kv : ivar_map) { + lbObjCGlobal const& g = kv.value; + lbAddr ivar_addr = {}; + lbValue *found = string_map_get(&m->members, g.global_name); + + if (found) { + ivar_addr = lb_addr(*found); + GB_ASSERT(ivar_addr.addr.type == t_int_ptr); + } else { + // Defined in an external package, define it now in the main package + LLVMTypeRef t = lb_type(m, t_int); + + lbValue global{}; + global.value = LLVMAddGlobal(m->mod, t, g.global_name); + global.type = t_int_ptr; + + LLVMSetInitializer(global.value, LLVMConstInt(t, 0, true)); + + ivar_addr = lb_addr(global); + } + + String class_name = g.class_impl_type->Named.type_name->TypeName.objc_class_name; + lbValue class_value = string_map_must_get(&global_class_map, class_name).class_value; + + args.count = 2; + args[0] = class_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar"))); + lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args); + + args.count = 1; + args[0] = ivar; + lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args); + lbValue ivar_offset_int = lb_emit_conv(p, ivar_offset, t_int); + + lb_addr_store(p, ivar_addr, ivar_offset_int); } lb_end_procedure_body(p); @@ -1278,6 +1907,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc lb_add_attribute_to_proc(p->module, p->value, "optnone"); lb_add_attribute_to_proc(p->module, p->value, "noinline"); + // Make sure shared libraries call their own runtime startup on Linux. + LLVMSetVisibility(p->value, LLVMHiddenVisibility); + LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); + lb_begin_procedure_body(p); lb_setup_type_info_data(main_module); @@ -1344,14 +1977,14 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::"); gbString e_str = string_canonical_entity_name(temporary_allocator(), e); var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str)); - lbAddr g = lb_add_global_generated_with_name(main_module, var_type, var.init, make_string_c(var_name)); + lbAddr g = lb_add_global_generated_with_name(main_module, var_type, {}, make_string_c(var_name)); lb_addr_store(p, g, var.init); lbValue gp = lb_addr_get_ptr(p, g); lbValue data = lb_emit_struct_ep(p, var.var, 0); lbValue ti = lb_emit_struct_ep(p, var.var, 1); lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr)); - lb_emit_store(p, ti, lb_type_info(p, var_type)); + lb_emit_store(p, ti, lb_typeid(p->module, var_type)); } else { LLVMTypeRef vt = llvm_addr_type(p->module, var.var); lbValue src0 = lb_emit_conv(p, var.init, t); @@ -1387,6 +2020,10 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C lb_add_attribute_to_proc(p->module, p->value, "optnone"); lb_add_attribute_to_proc(p->module, p->value, "noinline"); + // Make sure shared libraries call their own runtime cleanup on Linux. + LLVMSetVisibility(p->value, LLVMHiddenVisibility); + LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); + lb_begin_procedure_body(p); CheckerInfo *info = main_module->gen->info; @@ -2185,7 +2822,6 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { p->is_done = true; m->curr_procedure = nullptr; } - lb_end_procedure(p); // Add Flags if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) { @@ -2494,6 +3130,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); LLVMSetLinkage(g, LLVMInternalLinkage); lb_make_global_private_const(g); + lb_set_odin_rtti_section(g); return lb_addr({g, alloc_type_pointer(t)}); }; @@ -2565,24 +3202,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbValue g = {}; g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); g.type = alloc_type_pointer(e->type); - if (e->Variable.thread_local_model != "") { - LLVMSetThreadLocal(g.value, true); - String m = e->Variable.thread_local_model; - LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; - if (m == "default") { - mode = LLVMGeneralDynamicTLSModel; - } else if (m == "localdynamic") { - mode = LLVMLocalDynamicTLSModel; - } else if (m == "initialexec") { - mode = LLVMInitialExecTLSModel; - } else if (m == "localexec") { - mode = LLVMLocalExecTLSModel; - } else { - GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); - } - LLVMSetThreadLocalMode(g.value, mode); - } + lb_apply_thread_local_model(g.value, e->Variable.thread_local_model); + if (is_foreign) { LLVMSetLinkage(g.value, LLVMExternalLinkage); LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index a0764494a..fd6f50dcd 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -196,6 +196,7 @@ struct lbModule { StringMap objc_classes; StringMap objc_selectors; + StringMap objc_ivars; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info @@ -219,6 +220,7 @@ struct lbObjCGlobal { gbString global_name; String name; Type * type; + Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true. }; struct lbGenerator : LinkerData { @@ -240,6 +242,7 @@ struct lbGenerator : LinkerData { MPSCQueue entities_to_correct_linkage; MPSCQueue objc_selectors; MPSCQueue objc_classes; + MPSCQueue objc_ivars; MPSCQueue raddebug_section_strings; }; @@ -410,7 +413,6 @@ gb_internal LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef c gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value); gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name); gb_internal lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false); -gb_internal void lb_end_procedure(lbProcedure *p); gb_internal LLVMTypeRef lb_type(lbModule *m, Type *type); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index dada2cff5..51c8a4449 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -533,7 +533,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb Entity *e = entity_from_expr(expr); res = lb_find_procedure_value_from_entity(m, e); } - GB_ASSERT(res.value != nullptr); + if (res.value == nullptr) { + // This is an unspecialized polymorphic procedure, return nil or dummy value + return lb_const_nil(m, original_type); + } GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind); if (LLVMGetIntrinsicID(res.value) == 0) { diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 464d50cac..024c5564e 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -1374,3 +1374,59 @@ gb_internal void lb_add_debug_info_for_global_constant_from_entity(lbGenerator * } } } + +gb_internal void lb_add_debug_label(lbProcedure *p, Ast *label, lbBlock *target) { +// NOTE(tf2spi): LLVM-C DILabel API used only existed for major versions 20+ +#if LLVM_VERSION_MAJOR >= 20 + if (p == nullptr || p->debug_info == nullptr) { + return; + } + if (target == nullptr || label == nullptr || label->kind != Ast_Label) { + return; + } + Token label_token = label->Label.token; + if (is_blank_ident(label_token.string)) { + return; + } + lbModule *m = p->module; + if (m == nullptr) { + return; + } + + AstFile *file = label->file(); + LLVMMetadataRef llvm_file = lb_get_llvm_metadata(m, file); + if (llvm_file == nullptr) { + debugf("llvm file not found for label\n"); + return; + } + LLVMMetadataRef llvm_scope = p->debug_info; + if(llvm_scope == nullptr) { + debugf("llvm scope not found for label\n"); + return; + } + LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, label_token.pos); + LLVMBasicBlockRef llvm_block = target->block; + if (llvm_block == nullptr || llvm_debug_loc == nullptr) { + return; + } + LLVMMetadataRef llvm_label = LLVMDIBuilderCreateLabel( + m->debug_builder, + llvm_scope, + (const char *)label_token.string.text, + (size_t)label_token.string.len, + llvm_file, + label_token.pos.line, + + // NOTE(tf2spi): Defaults to false in LLVM API, but I'd rather not take chances + // Always preserve the label no matter what when debugging + true + ); + GB_ASSERT(llvm_label != nullptr); + (void)LLVMDIBuilderInsertLabelAtEnd( + m->debug_builder, + llvm_label, + llvm_debug_loc, + llvm_block + ); +#endif +} diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index b24f895d4..e17d958d7 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4844,7 +4844,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (cl->elems.count == 0) { break; } - GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals); + GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals || build_context.dynamic_literals); lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos); gb_unused(err); @@ -5154,8 +5154,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { return lb_build_addr(p, unparen_expr(se->selector)); } - - Type *type = base_type(tav.type); if (tav.mode == Addressing_Type) { // Addressing_Type Selection sel = lookup_field(tav.type, selector, true); if (sel.pseudo_field) { @@ -5190,18 +5188,29 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices); } - Selection sel = lookup_field(type, selector, false); + Selection sel = lookup_field(tav.type, selector, false); GB_ASSERT(sel.entity != nullptr); - if (sel.pseudo_field) { - GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup); + if (sel.pseudo_field && (sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup)) { Entity *e = entity_of_node(sel_node); GB_ASSERT(e->kind == Entity_Procedure); return lb_addr(lb_find_value_from_entity(p->module, e)); } - if (sel.is_bit_field) { - lbAddr addr = lb_build_addr(p, se->expr); + lbAddr addr = lb_build_addr(p, se->expr); + // NOTE(harold): Only allow ivar pseudo field access on indirect selectors. + // It is incoherent otherwise as Objective-C objects are zero-sized. + Type *deref_type = type_deref(tav.type); + if (tav.type->kind == Type_Pointer && deref_type->kind == Type_Named && deref_type->Named.type_name->TypeName.objc_ivar) { + // NOTE(harold): We need to load the ivar from the current address and + // replace addr with the loaded ivar addr to apply the selector load properly. + addr = lb_addr(lb_emit_load(p, addr.addr)); + + lbValue ivar_ptr = lb_handle_objc_ivar_for_objc_object_pointer(p, addr.addr); + addr = lb_addr(ivar_ptr); + } + + if (sel.is_bit_field) { Selection sub_sel = sel; sub_sel.index.count -= 1; @@ -5227,7 +5236,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { } { - lbAddr addr = lb_build_addr(p, se->expr); if (addr.kind == lbAddr_Map) { lbValue v = lb_addr_load(p, addr); lbValue a = lb_address_from_load_or_generate_local(p, v); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 2eaecd8a7..3a099ec55 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -101,6 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { string_map_init(&m->objc_classes); string_map_init(&m->objc_selectors); + string_map_init(&m->objc_ivars); map_init(&m->map_info_map, 0); map_init(&m->map_cell_info_map, 0); @@ -173,8 +174,9 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); mpsc_init(&gen->objc_selectors, heap_allocator()); mpsc_init(&gen->objc_classes, heap_allocator()); + mpsc_init(&gen->objc_ivars, heap_allocator()); mpsc_init(&gen->raddebug_section_strings, heap_allocator()); - + return true; } @@ -886,8 +888,8 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { Type *t = base_type(type_deref(addr.addr.type)); GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None); lbValue len = lb_soa_struct_len(p, addr.addr); - if (addr.soa.index_expr != nullptr) { - lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), index, len); + if (addr.soa.index_expr != nullptr && (!lb_is_const(addr.soa.index) || t->Struct.soa_kind != StructSoa_Fixed)) { + lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), addr.soa.index, len); } } @@ -2213,6 +2215,14 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { case Type_BitField: return lb_type_internal(m, type->BitField.backing_type); + + case Type_Generic: + if (type->Generic.specialized) { + return lb_type_internal(m, type->Generic.specialized); + } else { + // For unspecialized generics, use a pointer type as a placeholder + return LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0); + } } GB_PANIC("Invalid type %s", type_to_string(type)); @@ -2378,6 +2388,29 @@ gb_internal void lb_add_attribute_to_proc_with_string(lbModule *m, LLVMValueRef } +gb_internal bool lb_apply_thread_local_model(LLVMValueRef value, String model) { + if (model != "") { + LLVMSetThreadLocal(value, true); + + LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; + if (model == "default") { + mode = LLVMGeneralDynamicTLSModel; + } else if (model == "localdynamic") { + mode = LLVMLocalDynamicTLSModel; + } else if (model == "initialexec") { + mode = LLVMInitialExecTLSModel; + } else if (model == "localexec") { + mode = LLVMLocalExecTLSModel; + } else { + GB_PANIC("Unhandled thread local mode %.*s", LIT(model)); + } + LLVMSetThreadLocalMode(value, mode); + return true; + } + + return false; +} + gb_internal void lb_add_edge(lbBlock *from, lbBlock *to) { LLVMValueRef instr = LLVMGetLastInstruction(from->block); @@ -2516,10 +2549,13 @@ general_end:; } } - src_size = align_formula(src_size, src_align); - dst_size = align_formula(dst_size, dst_align); + // NOTE(laytan): even though this logic seems sound, the Address Sanitizer does not + // want you to load/store the space of a value that is there for alignment. +#if 0 + i64 aligned_src_size = align_formula(src_size, src_align); + i64 aligned_dst_size = align_formula(dst_size, dst_align); - if (LLVMIsALoadInst(val) && (src_size >= dst_size && src_align >= dst_align)) { + if (LLVMIsALoadInst(val) && (aligned_src_size >= aligned_dst_size && src_align >= dst_align)) { LLVMValueRef val_ptr = LLVMGetOperand(val, 0); val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), ""); LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr); @@ -2527,8 +2563,57 @@ general_end:; // LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align)); return loaded_val; + } +#endif + + if (src_size > dst_size) { + GB_ASSERT(p->decl_block != p->curr_block); + // NOTE(laytan): src is bigger than dst, need to memcpy the part of src we want. + + LLVMValueRef val_ptr; + if (LLVMIsALoadInst(val)) { + val_ptr = LLVMGetOperand(val, 0); + } else if (LLVMIsAAllocaInst(val)) { + val_ptr = LLVMBuildPointerCast(p->builder, val, LLVMPointerType(src_type, 0), ""); + } else { + // NOTE(laytan): we need a pointer to memcpy from. + LLVMValueRef val_copy = llvm_alloca(p, src_type, src_align); + val_ptr = LLVMBuildPointerCast(p->builder, val_copy, LLVMPointerType(src_type, 0), ""); + LLVMBuildStore(p->builder, val, val_ptr); + } + + i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type)); + max_align = gb_max(max_align, 16); + + LLVMValueRef ptr = llvm_alloca(p, dst_type, max_align); + LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(dst_type, 0), ""); + + LLVMTypeRef types[3] = { + lb_type(p->module, t_rawptr), + lb_type(p->module, t_rawptr), + lb_type(p->module, t_int) + }; + + LLVMValueRef args[4] = { + nptr, + val_ptr, + LLVMConstInt(LLVMIntTypeInContext(p->module->ctx, 8*cast(unsigned)build_context.int_size), dst_size, 0), + LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), 0, 0), + }; + + lb_call_intrinsic( + p, + "llvm.memcpy.inline", + args, + gb_count_of(args), + types, + gb_count_of(types) + ); + + return OdinLLVMBuildLoad(p, dst_type, ptr); } else { GB_ASSERT(p->decl_block != p->curr_block); + GB_ASSERT(dst_size >= src_size); i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type)); max_align = gb_max(max_align, 16); @@ -2729,6 +2814,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) ignore_body = other_module != m; lbProcedure *missing_proc = lb_create_procedure(m, e, ignore_body); + if (missing_proc == nullptr) { + // This is an unspecialized polymorphic procedure, which should not be codegen'd + lbValue dummy = {}; + dummy.value = nullptr; + dummy.type = nullptr; + return dummy; + } + if (ignore_body) { mutex_lock(&gen->anonymous_proc_lits_mutex); defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); @@ -2921,25 +3014,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); - if (e->Variable.thread_local_model != "") { - LLVMSetThreadLocal(g.value, true); - - String m = e->Variable.thread_local_model; - LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; - if (m == "default") { - mode = LLVMGeneralDynamicTLSModel; - } else if (m == "localdynamic") { - mode = LLVMLocalDynamicTLSModel; - } else if (m == "initialexec") { - mode = LLVMInitialExecTLSModel; - } else if (m == "localexec") { - mode = LLVMLocalExecTLSModel; - } else { - GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); - } - LLVMSetThreadLocalMode(g.value, mode); - } - + lb_apply_thread_local_model(g.value, e->Variable.thread_local_model); return g; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1f023037d..f51ed2b4d 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -67,6 +67,14 @@ gb_internal void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValu gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) { GB_ASSERT(entity != nullptr); GB_ASSERT(entity->kind == Entity_Procedure); + // Skip codegen for unspecialized polymorphic procedures + if (is_type_polymorphic(entity->type) && !entity->Procedure.is_foreign) { + Type *bt = base_type(entity->type); + if (bt->kind == Type_Proc && bt->Proc.is_polymorphic && !bt->Proc.is_poly_specialized) { + // Do not generate code for unspecialized polymorphic procedures + return nullptr; + } + } if (!entity->Procedure.is_foreign) { if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) { GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false)); @@ -783,8 +791,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) { p->curr_block = nullptr; p->state_flags = 0; -} -gb_internal void lb_end_procedure(lbProcedure *p) { + LLVMDisposeBuilder(p->builder); } @@ -817,6 +824,10 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) e->Procedure.link_name = name; lbProcedure *nested_proc = lb_create_procedure(p->module, e); + if (nested_proc == nullptr) { + // This is an unspecialized polymorphic procedure, skip codegen + return; + } e->code_gen_procedure = nested_proc; lbValue value = {}; @@ -2235,6 +2246,68 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return lb_emit_load(p, tuple); } + case BuiltinProc_compress_values: { + isize value_count = 0; + for (Ast *arg : ce->args) { + Type *t = arg->tav.type; + if (is_type_tuple(t)) { + value_count += t->Tuple.variables.count; + } else { + value_count += 1; + } + } + + if (value_count == 1) { + lbValue x = lb_build_expr(p, ce->args[0]); + x = lb_emit_conv(p, x, tv.type); + return x; + } + + Type *dt = base_type(tv.type); + lbAddr addr = lb_add_local_generated(p, tv.type, true); + if (is_type_struct(dt) || is_type_tuple(dt)) { + i32 index = 0; + for (Ast *arg : ce->args) { + lbValue x = lb_build_expr(p, arg); + if (is_type_tuple(x.type)) { + for (isize i = 0; i < x.type->Tuple.variables.count; i++) { + lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i); + lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++); + y = lb_emit_conv(p, y, type_deref(ptr.type)); + lb_emit_store(p, ptr, y); + } + } else { + lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++); + x = lb_emit_conv(p, x, type_deref(ptr.type)); + lb_emit_store(p, ptr, x); + } + } + GB_ASSERT(index == value_count); + } else if (is_type_array_like(dt)) { + i32 index = 0; + for (Ast *arg : ce->args) { + lbValue x = lb_build_expr(p, arg); + if (is_type_tuple(x.type)) { + for (isize i = 0; i < x.type->Tuple.variables.count; i++) { + lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i); + lbValue ptr = lb_emit_array_epi(p, addr.addr, index++); + y = lb_emit_conv(p, y, type_deref(ptr.type)); + lb_emit_store(p, ptr, y); + } + } else { + lbValue ptr = lb_emit_array_epi(p, addr.addr, index++); + x = lb_emit_conv(p, x, type_deref(ptr.type)); + lb_emit_store(p, ptr, x); + } + } + GB_ASSERT(index == value_count); + } else { + GB_PANIC("TODO(bill): compress_values -> %s", type_to_string(tv.type)); + } + + return lb_addr_load(p, addr); + } + case BuiltinProc_min: { Type *t = type_of_expr(expr); if (ce->args.count == 2) { @@ -3375,6 +3448,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr); case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr); case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr); + case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr); case BuiltinProc_constant_utf16_cstring: diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 89737a454..9b5b14626 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -136,7 +136,6 @@ gb_internal lbBranchBlocks lb_lookup_branch_blocks(lbProcedure *p, Ast *ident) { return empty; } - gb_internal lbTargetList *lb_push_target_list(lbProcedure *p, Ast *label, lbBlock *break_, lbBlock *continue_, lbBlock *fallthrough_) { lbTargetList *tl = gb_alloc_item(permanent_allocator(), lbTargetList); tl->prev = p->target_list; @@ -688,6 +687,18 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lbBlock *body = lb_create_block(p, "for.interval.body"); lbBlock *done = lb_create_block(p, "for.interval.done"); + // TODO(tf2spi): This is inlined in more than several places. + // Putting this in a function might be preferred. + // LLVMSetCurrentDebugLocation2 has side effects, + // so I didn't want to hide that before it got reviewed. + if (rs->label != nullptr && p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "for.interval.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, rs->label)); + lb_add_debug_label(p, rs->label, label); + } lb_emit_jump(p, loop); lb_start_block(p, loop); @@ -893,6 +904,14 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs lbAddr index = lb_add_local_generated(p, t_int, false); + if (rs->label != nullptr && p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "for.soa.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, rs->label)); + lb_add_debug_label(p, rs->label, label); + } if (!is_reverse) { /* for x, i in array { @@ -970,7 +989,6 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs lb_store_range_stmt_val(p, val1, lb_addr_load(p, index)); } - lb_push_target_list(p, rs->label, done, loop, nullptr); lb_build_stmt(p, rs->body); @@ -1029,6 +1047,15 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lbBlock *done = nullptr; bool is_map = false; + if (rs->label != nullptr && p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "for.range.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, rs->label)); + lb_add_debug_label(p, rs->label, label); + } + if (tav.mode == Addressing_Type) { lb_build_range_enum(p, type_deref(tav.type), val0_type, &val, &key, &loop, &done); } else { @@ -1530,6 +1557,14 @@ gb_internal bool lb_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss, boo gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope *scope) { lb_open_scope(p, scope); + if (ss->label != nullptr && p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "switch.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, ss->label)); + lb_add_debug_label(p, ss->label, label); + } if (ss->init != nullptr) { lb_build_stmt(p, ss->init); } @@ -1736,6 +1771,7 @@ gb_internal lbAddr lb_store_range_stmt_val(lbProcedure *p, Ast *stmt_val, lbValu gb_internal void lb_type_case_body(lbProcedure *p, Ast *label, Ast *clause, lbBlock *body, lbBlock *done) { ast_node(cc, CaseClause, clause); + // NOTE(tf2spi): Debug info for label not generated here on purpose lb_push_target_list(p, label, done, nullptr, nullptr); lb_build_stmt_list(p, cc->stmts); lb_close_scope(p, lbDeferExit_Default, body, clause); @@ -1986,33 +2022,43 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) { LLVMValueRef global = LLVMAddGlobal(p->module->mod, lb_type(p->module, e->type), c_name); LLVMSetAlignment(global, cast(u32)type_align_of(e->type)); LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type))); - if (value.value != nullptr) { - LLVMSetInitializer(global, value.value); - } + if (e->Variable.is_rodata) { LLVMSetGlobalConstant(global, true); } - if (e->Variable.thread_local_model != "") { - LLVMSetThreadLocal(global, true); - String m = e->Variable.thread_local_model; - LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; - if (m == "default") { - mode = LLVMGeneralDynamicTLSModel; - } else if (m == "localdynamic") { - mode = LLVMLocalDynamicTLSModel; - } else if (m == "initialexec") { - mode = LLVMInitialExecTLSModel; - } else if (m == "localexec") { - mode = LLVMLocalExecTLSModel; - } else { - GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); - } - LLVMSetThreadLocalMode(global, mode); - } else { + if (!lb_apply_thread_local_model(global, e->Variable.thread_local_model)) { LLVMSetLinkage(global, LLVMInternalLinkage); } + if (value.value != nullptr) { + if (is_type_any(e->type)) { + Type *var_type = default_type(value.type); + + gbString var_name = gb_string_make(temporary_allocator(), "__$static_any::"); + var_name = gb_string_append_length(var_name, mangled_name.text, mangled_name.len); + + lbAddr var_global = lb_add_global_generated_with_name(p->module, var_type, value, make_string_c(var_name), nullptr); + LLVMValueRef var_global_ref = var_global.addr.value; + + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(var_global_ref, true); + } + + if (!lb_apply_thread_local_model(var_global_ref, e->Variable.thread_local_model)) { + LLVMSetLinkage(var_global_ref, LLVMInternalLinkage); + } + + LLVMValueRef vals[2] = { + lb_emit_conv(p, var_global.addr, t_rawptr).value, + lb_typeid(p->module, var_type).value, + }; + LLVMValueRef init = llvm_const_named_struct(p->module, e->type, vals, gb_count_of(vals)); + LLVMSetInitializer(global, init); + } else { + LLVMSetInitializer(global, value.value); + } + } lbValue global_val = {global, alloc_type_pointer(e->type)}; lb_add_entity(p->module, e, global_val); @@ -2307,6 +2353,14 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { else_ = lb_create_block(p, "if.else"); } if (is->label != nullptr) { + if (p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "if.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, is->label)); + lb_add_debug_label(p, is->label, label); + } lbTargetList *tl = lb_push_target_list(p, is->label, done, nullptr, nullptr); tl->is_block = true; } @@ -2399,12 +2453,19 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { lb_push_target_list(p, fs->label, done, post, nullptr); + if (fs->label != nullptr && p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "for.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, fs->label)); + lb_add_debug_label(p, fs->label, label); + } if (fs->init != nullptr) { - #if 1 lbBlock *init = lb_create_block(p, "for.init"); lb_emit_jump(p, init); lb_start_block(p, init); - #endif + lb_build_stmt(p, fs->init); } @@ -2420,7 +2481,6 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { lb_start_block(p, body); } - lb_build_stmt(p, fs->body); lb_pop_target_list(p); @@ -2694,9 +2754,21 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { case_ast_node(bs, BlockStmt, node); + lbBlock *body = nullptr; lbBlock *done = nullptr; if (bs->label != nullptr) { + if (p->debug_info != nullptr) { + lbBlock *label = lb_create_block(p, "block.label"); + lb_emit_jump(p, label); + lb_start_block(p, label); + + LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, bs->label)); + lb_add_debug_label(p, bs->label, label); + } + body = lb_create_block(p, "block.body"); done = lb_create_block(p, "block.done"); + lb_emit_jump(p, body); + lb_start_block(p, body); lbTargetList *tl = lb_push_target_list(p, bs->label, done, nullptr, nullptr); tl->is_block = true; } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index ad4250f3c..4e514c3d1 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -1,4 +1,10 @@ +gb_internal void lb_set_odin_rtti_section(LLVMValueRef value) { + if (build_context.metrics.os != TargetOs_darwin) { + LLVMSetSection(value, ".odinti"); + } +} + gb_internal isize lb_type_info_index(CheckerInfo *info, TypeInfoPair pair, bool err_on_not_found=true) { isize index = type_info_index(info, pair, err_on_not_found); if (index >= 0) { @@ -221,6 +227,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ gb_snprintf(name, 63, "__$ti-%lld", cast(long long)index); LLVMValueRef g = LLVMAddGlobal(m->mod, type, name); lb_make_global_private_const(g); + lb_set_odin_rtti_section(g); return g; }; @@ -716,6 +723,8 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ LLVMSetInitializer(value_array.value, value_init); LLVMSetGlobalConstant(name_array.value, true); LLVMSetGlobalConstant(value_array.value, true); + lb_set_odin_rtti_section(name_array.value); + lb_set_odin_rtti_section(value_array.value); lbValue v_count = lb_const_int(m, t_int, fields.count); @@ -1056,6 +1065,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value; LLVMSetInitializer(giant_array, giant_const); lb_make_global_private_const(giant_array); + lb_set_odin_rtti_section(giant_array); } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index bfeebfcbe..521553147 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2125,7 +2125,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri return addr; } -gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { +gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name, Type *class_impl_type) { lbModule *m = p->module; lbAddr *found = string_map_get(&m->objc_classes, name); if (found) { @@ -2148,13 +2148,75 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String } else { LLVMSetLinkage(g.value, LLVMExternalLinkage); } - mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class}); + mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class, class_impl_type}); lbAddr addr = lb_addr(g); string_map_set(&m->objc_classes, name, addr); return addr; } +gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_type) { + + String name = self_type->Named.type_name->TypeName.objc_class_name; + GB_ASSERT(name != ""); + + lbAddr *found = string_map_get(&m->objc_ivars, name); + if (found) { + return *found; + } + + lbModule *default_module = &m->gen->default_module; + + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_ivar::"); + global_name = gb_string_append_length(global_name, name.text, name.len); + + // Create a global variable to store offset of the ivar in an instance of an object + LLVMTypeRef t = lb_type(m, t_int); + + lbValue g = {}; + g.value = LLVMAddGlobal(m->mod, t, global_name); + g.type = t_int_ptr; + + if (default_module == m) { + LLVMSetInitializer(g.value, LLVMConstInt(t, 0, true)); + lb_add_member(m, make_string_c(global_name), g); + } else { + LLVMSetLinkage(g.value, LLVMExternalLinkage); + } + + mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, t_int, self_type}); + + lbAddr addr = lb_addr(g); + string_map_set(&m->objc_ivars, name, addr); + return addr; +} + +gb_internal lbValue lb_handle_objc_ivar_for_objc_object_pointer(lbProcedure *p, lbValue self) { + GB_ASSERT(self.type->kind == Type_Pointer && self.type->Pointer.elem->kind == Type_Named); + + Type *self_type = self.type->Pointer.elem; + + lbValue self_uptr = lb_emit_conv(p, self, t_uintptr); + + lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(p->module, self_type)); + lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr); + + + lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr); + + Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar; + return lb_emit_conv(p, ivar_uptr, alloc_type_pointer(ivar_type)); +} + +gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + + GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer); + lbValue self = lb_build_expr(p, ce->args[0]); + + return lb_handle_objc_ivar_for_objc_object_pointer(p, self); +} + gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { ast_node(ce, CallExpr, expr); @@ -2188,7 +2250,7 @@ gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { auto tav = ce->args[0]->tav; GB_ASSERT(tav.value.kind == ExactValue_String); String name = tav.value.value_string; - return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, nullptr)); } gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) { @@ -2198,7 +2260,7 @@ gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) { auto tav = ce->args[0]->tav; GB_ASSERT(tav.value.kind == ExactValue_String); String name = tav.value.value_string; - lbAddr dst = lb_handle_objc_find_or_register_class(p, name); + lbAddr dst = lb_handle_objc_find_or_register_class(p, name, nullptr); auto args = array_make(permanent_allocator(), 3); args[0] = lb_const_nil(m, t_objc_Class); @@ -2220,7 +2282,9 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) { GB_ASSERT(e->kind == Entity_TypeName); String name = e->TypeName.objc_class_name; - return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); + Type *class_impl_type = e->TypeName.objc_is_implementation ? type : nullptr; + + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, class_impl_type)); } return lb_build_expr(p, expr); @@ -2266,9 +2330,6 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { return lb_emit_call(p, the_proc, args); } - - - gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { GB_ASSERT(value.kind == ExactValue_Integer); i64 v = exact_value_to_i64(value); diff --git a/src/main.cpp b/src/main.cpp index b2cfbe018..90f2aad7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -278,10 +278,10 @@ gb_internal void usage(String argv0, String argv1 = {}) { print_usage_line(1, " One must contain the program's entry point, all must be in the same package."); print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable."); print_usage_line(1, "bundle Bundles a directory in a specific layout for that platform."); - print_usage_line(1, "check Parses, and type checks a directory of .odin files."); + print_usage_line(1, "check Parses and type checks a directory of .odin files."); print_usage_line(1, "strip-semicolon Parses, type checks, and removes unneeded semicolons from the entire program."); print_usage_line(1, "test Builds and runs procedures with the attribute @(test) in the initial package."); - print_usage_line(1, "doc Generates documentation on a directory of .odin files."); + print_usage_line(1, "doc Generates documentation from a directory of .odin files."); print_usage_line(1, "version Prints version."); print_usage_line(1, "report Prints information useful to reporting a bug."); print_usage_line(1, "root Prints the root path where Odin looks for the builtin collections."); @@ -319,6 +319,7 @@ enum BuildFlagKind { BuildFlag_NoBoundsCheck, BuildFlag_NoTypeAssert, BuildFlag_NoDynamicLiterals, + BuildFlag_DynamicLiterals, BuildFlag_NoCRT, BuildFlag_NoRPath, BuildFlag_NoEntryPoint, @@ -538,6 +539,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_NoTypeAssert, str_lit("no-type-assert"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoThreadLocal, str_lit("no-thread-local"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_DynamicLiterals, str_lit("dynamic-literals"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoRPath, str_lit("no-rpath"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); @@ -1207,6 +1209,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_NoDynamicLiterals: gb_printf_err("Warning: Use of -no-dynamic-literals is now redundant\n"); break; + case BuildFlag_DynamicLiterals: + build_context.dynamic_literals = true; + break; case BuildFlag_NoCRT: build_context.no_crt = true; break; @@ -2221,20 +2226,30 @@ gb_internal void remove_temp_files(lbGenerator *gen) { } -gb_internal void print_show_help(String const arg0, String command, String optional_flag = {}) { +gb_internal int print_show_help(String const arg0, String command, String optional_flag = {}) { + bool help_resolved = false; + bool printed_usage_header = false; + bool printed_flags_header = false; + if (command == "help" && optional_flag.len != 0 && optional_flag[0] != '-') { command = optional_flag; optional_flag = {}; } - print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(arg0)); - print_usage_line(0, "Usage:"); - print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command)); - print_usage_line(0, ""); - defer (print_usage_line(0, "")); - + auto const print_usage_header_once = [&help_resolved, &printed_usage_header, arg0, command]() { + if (printed_usage_header) { + return; + } + print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(arg0)); + print_usage_line(0, "Usage:"); + print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command)); + print_usage_line(0, ""); + help_resolved = true; + printed_usage_header = true; + }; if (command == "build") { + print_usage_header_once(); print_usage_line(1, "build Compiles directory of .odin files as an executable."); print_usage_line(2, "One must contain the program's entry point, all must be in the same package."); print_usage_line(2, "Use `-file` to build a single file instead."); @@ -2243,6 +2258,7 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(3, "odin build Builds package in ."); print_usage_line(3, "odin build filename.odin -file Builds single-file package, must contain entry point."); } else if (command == "run") { + print_usage_header_once(); print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable."); print_usage_line(2, "Append an empty flag and then the args, '-- ', to specify args for the output."); print_usage_line(2, "Examples:"); @@ -2250,28 +2266,40 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(3, "odin run Builds and runs package in ."); print_usage_line(3, "odin run filename.odin -file Builds and runs single-file package, must contain entry point."); } else if (command == "check") { + print_usage_header_once(); print_usage_line(1, "check Parses and type checks directory of .odin files."); print_usage_line(2, "Examples:"); print_usage_line(3, "odin check . Type checks package in current directory."); print_usage_line(3, "odin check Type checks package in ."); print_usage_line(3, "odin check filename.odin -file Type checks single-file package, must contain entry point."); } else if (command == "test") { + print_usage_header_once(); print_usage_line(1, "test Builds and runs procedures with the attribute @(test) in the initial package."); } else if (command == "doc") { + print_usage_header_once(); print_usage_line(1, "doc Generates documentation from a directory of .odin files."); print_usage_line(2, "Examples:"); print_usage_line(3, "odin doc . Generates documentation on package in current directory."); print_usage_line(3, "odin doc Generates documentation on package in ."); print_usage_line(3, "odin doc filename.odin -file Generates documentation on single-file package."); } else if (command == "version") { + print_usage_header_once(); print_usage_line(1, "version Prints version."); } else if (command == "strip-semicolon") { + print_usage_header_once(); print_usage_line(1, "strip-semicolon"); print_usage_line(2, "Parses and type checks .odin file(s) and then removes unneeded semicolons from the entire project."); } else if (command == "bundle") { + print_usage_header_once(); print_usage_line(1, "bundle Bundles a directory in a specific layout for that platform"); print_usage_line(2, "Supported platforms:"); print_usage_line(3, "android"); + } else if (command == "report") { + print_usage_header_once(); + print_usage_line(1, "report Prints information useful to reporting a bug."); + } else if (command == "root") { + print_usage_header_once(); + print_usage_line(1, "root Prints the root path where Odin looks for the builtin collections."); } bool doc = command == "doc"; @@ -2293,13 +2321,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio check = true; } - print_usage_line(0, ""); - print_usage_line(1, "Flags"); - print_usage_line(0, ""); - auto const print_flag = [&optional_flag](char const *flag) -> bool { + auto const print_flag = [&optional_flag, &help_resolved, &printed_flags_header, print_usage_header_once](char const *flag) -> bool { if (optional_flag.len != 0) { String f = make_string_c(flag); isize i = string_index_byte(f, ':'); @@ -2310,6 +2335,14 @@ gb_internal void print_show_help(String const arg0, String command, String optio return false; } } + print_usage_header_once(); + if (!printed_flags_header) { + print_usage_line(0, ""); + print_usage_line(1, "Flags"); + print_usage_line(0, ""); + printed_flags_header = true; + } + help_resolved = true; print_usage_line(0, ""); print_usage_line(1, flag); return true; @@ -2331,20 +2364,20 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-build-mode:")) { print_usage_line(2, "Sets the build mode."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-build-mode:exe Builds as an executable."); - print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); - print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); - print_usage_line(3, "-build-mode:static Builds as a statically linked library."); - print_usage_line(3, "-build-mode:obj Builds as an object file."); - print_usage_line(3, "-build-mode:object Builds as an object file."); - print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); - print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); - print_usage_line(3, "-build-mode:asm Builds as an assembly file."); - print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); - print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); + print_usage_line(3, "-build-mode:exe Builds as an executable."); + print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); + print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); + print_usage_line(3, "-build-mode:static Builds as a statically linked library."); + print_usage_line(3, "-build-mode:obj Builds as an object file."); + print_usage_line(3, "-build-mode:object Builds as an object file."); + print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); + print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); + print_usage_line(3, "-build-mode:asm Builds as an assembly file."); + print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); + print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); } } @@ -2353,16 +2386,16 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(2, "Defines a library collection used for imports."); print_usage_line(2, "Example: -collection:shared=dir/to/shared"); print_usage_line(2, "Usage in Code:"); - print_usage_line(3, "import \"shared:foo\""); + print_usage_line(3, "import \"shared:foo\""); } if (print_flag("-custom-attribute:")) { print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown."); print_usage_line(2, "This can be used with metaprogramming tools."); print_usage_line(2, "Examples:"); - print_usage_line(3, "-custom-attribute:my_tag"); - print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); - print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag"); + print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); } } @@ -2385,7 +2418,7 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); print_usage_line(2, "Example: -define:SPAM=123"); print_usage_line(2, "Usage in code:"); - print_usage_line(3, "#config(SPAM, default_value)"); + print_usage_line(3, "#config(SPAM, default_value)"); } } @@ -2420,9 +2453,9 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (check) { if (print_flag("-error-pos-style:")) { print_usage_line(2, "Available options:"); - print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); - print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); - print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); + print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); + print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); + print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); } if (print_flag("-export-defineables:")) { @@ -2433,8 +2466,8 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-export-dependencies:")) { print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); - print_usage_line(3, "-export-dependencies:json Exports in JSON format"); + print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); + print_usage_line(3, "-export-dependencies:json Exports in JSON format"); } if (print_flag("-export-dependencies-file:")) { @@ -2445,8 +2478,8 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-export-timings:")) { print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); - print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); + print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); + print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); } if (print_flag("-export-timings-file:")) { @@ -2536,9 +2569,9 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-microarch:")) { print_usage_line(2, "Specifies the specific micro-architecture for the build in a string."); print_usage_line(2, "Examples:"); - print_usage_line(3, "-microarch:sandybridge"); - print_usage_line(3, "-microarch:native"); - print_usage_line(3, "-microarch:\"?\" for a list"); + print_usage_line(3, "-microarch:sandybridge"); + print_usage_line(3, "-microarch:native"); + print_usage_line(3, "-microarch:\"?\" for a list"); } } @@ -2595,10 +2628,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-o:")) { print_usage_line(2, "Sets the optimization mode for compilation."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-o:none"); - print_usage_line(3, "-o:minimal"); - print_usage_line(3, "-o:size"); - print_usage_line(3, "-o:speed"); + print_usage_line(3, "-o:none"); + print_usage_line(3, "-o:minimal"); + print_usage_line(3, "-o:size"); + print_usage_line(3, "-o:speed"); if (LB_USE_NEW_PASS_SYSTEM) { print_usage_line(3, "-o:aggressive (use this with caution)"); } @@ -2649,10 +2682,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-reloc-mode:")) { print_usage_line(2, "Specifies the reloc mode."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-reloc-mode:default"); - print_usage_line(3, "-reloc-mode:static"); - print_usage_line(3, "-reloc-mode:pic"); - print_usage_line(3, "-reloc-mode:dynamic-no-pic"); + print_usage_line(3, "-reloc-mode:default"); + print_usage_line(3, "-reloc-mode:static"); + print_usage_line(3, "-reloc-mode:pic"); + print_usage_line(3, "-reloc-mode:dynamic-no-pic"); } #if defined(GB_SYSTEM_WINDOWS) @@ -2667,9 +2700,9 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-sanitize:")) { print_usage_line(2, "Enables sanitization analysis."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-sanitize:address"); - print_usage_line(3, "-sanitize:memory"); - print_usage_line(3, "-sanitize:thread"); + print_usage_line(3, "-sanitize:address"); + print_usage_line(3, "-sanitize:memory"); + print_usage_line(3, "-sanitize:thread"); print_usage_line(2, "NOTE: This flag can be used multiple times."); } } @@ -2730,17 +2763,32 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(2, "[Windows only]"); print_usage_line(2, "Defines the subsystem for the application."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-subsystem:console"); - print_usage_line(3, "-subsystem:windows"); + print_usage_line(3, "-subsystem:console"); + print_usage_line(3, "-subsystem:windows"); } #endif + } + if (build) { + if (print_flag("-subtarget:")) { + print_usage_line(2, "[Darwin and Linux only]"); + print_usage_line(2, "Available subtargets:"); + String prefix = str_lit("-subtarget:"); + for (u32 i = 1; i < Subtarget_COUNT; i++) { + String name = subtarget_strings[i]; + String help_string = concatenate_strings(temporary_allocator(), prefix, name); + print_usage_line(3, (const char *)help_string.text); + } + } + } + + if (run_or_build) { if (print_flag("-target-features:")) { print_usage_line(2, "Specifies CPU features to enable on top of the enabled features implied by -microarch."); print_usage_line(2, "Examples:"); - print_usage_line(3, "-target-features:atomics"); - print_usage_line(3, "-target-features:\"sse2,aes\""); - print_usage_line(3, "-target-features:\"?\" for a list"); + print_usage_line(3, "-target-features:atomics"); + print_usage_line(3, "-target-features:\"sse2,aes\""); + print_usage_line(3, "-target-features:\"?\" for a list"); } } @@ -2777,11 +2825,11 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (print_flag("-vet")) { print_usage_line(2, "Does extra checks on the code."); print_usage_line(2, "Extra checks include:"); - print_usage_line(3, "-vet-unused"); - print_usage_line(3, "-vet-unused-variables"); - print_usage_line(3, "-vet-unused-imports"); - print_usage_line(3, "-vet-shadowing"); - print_usage_line(3, "-vet-using-stmt"); + print_usage_line(3, "-vet-unused"); + print_usage_line(3, "-vet-unused-variables"); + print_usage_line(3, "-vet-unused-imports"); + print_usage_line(3, "-vet-shadowing"); + print_usage_line(3, "-vet-using-stmt"); } if (print_flag("-vet-cast")) { @@ -2867,6 +2915,21 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(2, "If this is omitted, the terminal will prompt you to provide it."); } } + + if (!help_resolved) { + usage(arg0); + print_usage_line(0, ""); + if (command == "help") { + print_usage_line(0, "'%.*s' is not a recognized flag.", LIT(optional_flag)); + } else { + print_usage_line(0, "'%.*s' is not a recognized command.", LIT(command)); + } + return 1; + } + + print_usage_line(0, ""); + + return 0; } gb_internal void print_show_unused(Checker *c) { @@ -3239,6 +3302,16 @@ int main(int arg_count, char const **arg_ptr) { String run_args_string = {}; isize last_non_run_arg = args.count; + for_array(i, args) { + if (args[i] == "--") { + break; + } + if (args[i] == "-help" || args[i] == "--help") { + build_context.show_help = true; + return print_show_help(args[0], command); + } + } + bool run_output = false; if (command == "run" || command == "test") { if (args.count < 3) { @@ -3332,6 +3405,10 @@ int main(int arg_count, char const **arg_ptr) { return 1; #endif } else if (command == "version") { + if (args.count != 2) { + usage(args[0]); + return 1; + } build_context.command_kind = Command_version; gb_printf("%.*s version %.*s", LIT(args[0]), LIT(ODIN_VERSION)); @@ -3346,6 +3423,10 @@ int main(int arg_count, char const **arg_ptr) { gb_printf("\n"); return 0; } else if (command == "report") { + if (args.count != 2) { + usage(args[0]); + return 1; + } build_context.command_kind = Command_bug_report; print_bug_report_help(); return 0; @@ -3354,8 +3435,7 @@ int main(int arg_count, char const **arg_ptr) { usage(args[0]); return 1; } else { - print_show_help(args[0], args[1], args[2]); - return 0; + return print_show_help(args[0], args[1], args[2]); } } else if (command == "bundle") { if (args.count < 4) { @@ -3371,6 +3451,10 @@ int main(int arg_count, char const **arg_ptr) { } init_filename = args[3]; } else if (command == "root") { + if (args.count != 2) { + usage(args[0]); + return 1; + } gb_printf("%.*s", LIT(odin_root_dir())); return 0; } else if (command == "clear-cache") { @@ -3386,11 +3470,6 @@ int main(int arg_count, char const **arg_ptr) { init_filename = copy_string(permanent_allocator(), init_filename); - if (init_filename == "-help" || - init_filename == "--help") { - build_context.show_help = true; - } - if (init_filename.len > 0 && !build_context.show_help) { // The command must be build, run, test, check, or another that takes a directory or filename. if (!path_is_directory(init_filename)) { @@ -3441,8 +3520,7 @@ int main(int arg_count, char const **arg_ptr) { } if (build_context.show_help) { - print_show_help(args[0], command); - return 0; + return print_show_help(args[0], command); } if (command == "bundle") { diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 6aa933e86..0372f5039 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -756,8 +756,12 @@ gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) { type_writer_appendc(w, "/"); write_type_to_canonical_string(w, type->Generic.specialized); } + } else if (type->Generic.specialized) { + // If we have a specialized type, use that instead of panicking + write_type_to_canonical_string(w, type->Generic.specialized); } else { - GB_PANIC("Type_Generic should never be hit"); + // For unspecialized generics, use a generic placeholder string + type_writer_appendc(w, "rawptr"); } return; diff --git a/src/types.cpp b/src/types.cpp index 393e35ca1..ce921796d 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -729,10 +729,12 @@ gb_global Type *t_map_set_proc = nullptr; gb_global Type *t_objc_object = nullptr; gb_global Type *t_objc_selector = nullptr; gb_global Type *t_objc_class = nullptr; +gb_global Type *t_objc_ivar = nullptr; gb_global Type *t_objc_id = nullptr; gb_global Type *t_objc_SEL = nullptr; gb_global Type *t_objc_Class = nullptr; +gb_global Type *t_objc_Ivar = nullptr; enum OdinAtomicMemoryOrder : i32 { OdinAtomicMemoryOrder_relaxed = 0, // unordered @@ -872,6 +874,29 @@ gb_internal Type *base_type(Type *t) { return t; } +gb_internal Type *base_named_type(Type *t) { + if (t->kind != Type_Named) { + return t_invalid; + } + + Type *prev_named = t; + t = t->Named.base; + for (;;) { + if (t == nullptr) { + break; + } + if (t->kind != Type_Named) { + break; + } + if (t == t->Named.base) { + return t_invalid; + } + prev_named = t; + t = t->Named.base; + } + return prev_named; +} + gb_internal Type *base_enum_type(Type *t) { Type *bt = base_type(t); if (bt != nullptr && @@ -2932,6 +2957,10 @@ gb_internal Type *default_type(Type *type) { case Basic_UntypedString: return t_string; case Basic_UntypedRune: return t_rune; } + } else if (type->kind == Type_Generic) { + if (type->Generic.specialized) { + return default_type(type->Generic.specialized); + } } return type; } @@ -3327,6 +3356,15 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } } } + + Type *objc_ivar_type = e->TypeName.objc_ivar; + if (objc_ivar_type != nullptr) { + sel = lookup_field_with_selection(objc_ivar_type, field_name, false, sel, allow_blank_ident); + if (sel.entity != nullptr) { + sel.pseudo_field = true; + return sel; + } + } } if (is_type_polymorphic(type)) { diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin index 2cf1f1f1c..7b1cb0146 100644 --- a/tests/core/os/os2/path.odin +++ b/tests/core/os/os2/path.odin @@ -36,47 +36,58 @@ posix_to_dos_path :: proc(path: string) -> string { @(test) test_clean_path :: proc(t: ^testing.T) { Test_Case :: struct{ - path: string, + path: string, expected: string, } - test_cases := [?]Test_Case { - {`../../foo/../../`, `../../..`}, - {`../../foo/..`, `../..`}, - {`../../foo`, `../../foo`}, - {`../..`, `../..`}, - {`.././foo`, `../foo`}, - {`..`, `..`}, - {`.`, `.`}, - {`.foo`, `.foo`}, - {`/../../foo/../../`, `/`}, - {`/../`, `/`}, - {`/..`, `/`}, - {`/`, `/`}, - {`//home/foo/bar/../../`, `/home`}, - {`/a/../..`, `/`}, - {`/a/../`, `/`}, - {`/a/あ`, `/a/あ`}, - {`/a/あ/..`, `/a`}, - {`/あ/a/..`, `/あ`}, - {`/あ/a/../あ`, `/あ/あ`}, - {`/home/../`, `/`}, - {`/home/..`, `/`}, - {`/home/foo/../../usr`, `/usr`}, - {`/home/foo/../..`, `/`}, - {`/home/foo/../`, `/home`}, - {``, `.`}, - {`a/..`, `.`}, - {`a`, `a`}, - {`abc//.//../foo`, `foo`}, - {`foo`, `foo`}, - {`home/foo/bar/../../`, `home`}, - } - when ODIN_OS == .Windows { - for &tc in test_cases { - tc.path = posix_to_dos_path(tc.path) - tc.expected = posix_to_dos_path(tc.expected) + test_cases := [?]Test_Case { + {`W:/odin\examples\demo/demo.odin`, `W:\odin\examples\demo\demo.odin`}, + {`\\server\share\path\file.ext`, `\\server\share\path\file.ext`}, + {`//server\share/path\file.ext`, `\\server\share\path\file.ext`}, + {`/\192.168.0.10\share/path\file.ext`, `\\192.168.0.10\share\path\file.ext`}, + {`\\?\C:/Users/Foo/path\file.ext`, `\\?\C:\Users\Foo\path\file.ext`}, + {`\\?\\localhost\share\file.ext`, `\\?\\localhost\share\file.ext`}, + {`//?\/192.168.0.10\share\file.ext`, `\\?\\192.168.0.10\share\file.ext`}, + {`\\.\PhysicalDrive3`, `\\.\PhysicalDrive3`}, + {`/\./PhysicalDrive3`, `\\.\PhysicalDrive3`}, + {`C:\a\..\..`, `C:\`}, + {`C:\a\..`, `C:\`}, + {`C:\あ/a/..`, `C:\あ`}, + {`C:\あ/a/../あ`, `C:\あ\あ`}, + } + } else { + test_cases := [?]Test_Case { + {`../../foo/../../`, `../../..`}, + {`../../foo/..`, `../..`}, + {`../../foo`, `../../foo`}, + {`../..`, `../..`}, + {`.././foo`, `../foo`}, + {`..`, `..`}, + {`.`, `.`}, + {`.foo`, `.foo`}, + {`/../../foo/../../`, `/`}, + {`/../`, `/`}, + {`/..`, `/`}, + {`/`, `/`}, + {`//home/foo/bar/../../`, `/home`}, + {`/a/../..`, `/`}, + {`/a/../`, `/`}, + {`/a/あ`, `/a/あ`}, + {`/a/あ/..`, `/a`}, + {`/あ/a/..`, `/あ`}, + {`/あ/a/../あ`, `/あ/あ`}, + {`/home/../`, `/`}, + {`/home/..`, `/`}, + {`/home/foo/../../usr`, `/usr`}, + {`/home/foo/../..`, `/`}, + {`/home/foo/../`, `/home`}, + {``, `.`}, + {`a/..`, `.`}, + {`a`, `a`}, + {`abc//.//../foo`, `foo`}, + {`foo`, `foo`}, + {`home/foo/bar/../../`, `home`}, } } diff --git a/tests/core/os/os2/process.odin b/tests/core/os/os2/process.odin index d7700d201..c530b4c79 100644 --- a/tests/core/os/os2/process.odin +++ b/tests/core/os/os2/process.odin @@ -1,3 +1,4 @@ +#+build !windows package tests_core_os_os2 import os "core:os/os2" diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index 6b70654cc..8266ece23 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -30,6 +30,43 @@ test_float :: proc(t: ^testing.T) { testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + f, ok = strconv.parse_f64("0", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 1) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("0h", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 1) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("0h1", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("0h0000_0001", &n) + testing.expect_value(t, f, 0h0000_0001) + testing.expect_value(t, n, 11) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("0h4c60", &n) + testing.expect_value(t, f, 0h4c60) + testing.expect_value(t, f, 17.5) + testing.expect_value(t, n, 6) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("0h418c0000", &n) + testing.expect_value(t, f, 0h418c0000) + testing.expect_value(t, f, 17.5) + testing.expect_value(t, n, 10) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("0h4031_8000_0000_0000", &n) + testing.expect_value(t, f, 0h4031800000000000) + testing.expect_value(t, f, f64(17.5)) + testing.expect_value(t, n, 21) + testing.expect_value(t, ok, true) } @(test) diff --git a/tests/internal/test_global_any.odin b/tests/internal/test_global_any.odin new file mode 100644 index 000000000..73b70e0a4 --- /dev/null +++ b/tests/internal/test_global_any.odin @@ -0,0 +1,40 @@ +package test_internal + +@(private="file") +global_any_from_proc: any = from_proc() + +from_proc :: proc() -> f32 { + return 1.1 +} + +@(private="file") +global_any: any = 1 + +import "core:testing" + +@(test) +test_global_any :: proc(t: ^testing.T) { + as_f32, is_f32 := global_any_from_proc.(f32) + testing.expect(t, is_f32 == true) + testing.expect(t, as_f32 == 1.1) + + as_int, is_int := global_any.(int) + testing.expect(t, is_int == true) + testing.expect(t, as_int == 1) +} + +@(test) +test_static_any :: proc(t: ^testing.T) { + @(static) + var: any = 3 + + as_int, is_int := var.(int) + testing.expect(t, is_int == true) + testing.expect(t, as_int == 3) + + var = f32(1.1) + + as_f32, is_f32 := var.(f32) + testing.expect(t, is_f32 == true) + testing.expect(t, as_f32 == 1.1) +} diff --git a/tests/internal/test_intrinsics_integer_to.odin b/tests/internal/test_intrinsics_integer_to.odin new file mode 100644 index 000000000..108318c9a --- /dev/null +++ b/tests/internal/test_intrinsics_integer_to.odin @@ -0,0 +1,34 @@ +package test_internal + +import "base:intrinsics" +import "core:testing" + +/* +example_usage :: proc(#any_int x: int) -> intrinsics.type_integer_to_unsigned(type_of(x)) { + T :: intrinsics.type_integer_to_unsigned(type_of(x)) + return 1< result --- /* - Resets the resampler's timer and clears it's internal cache. + Resets the resampler's timer and clears its internal cache. */ resampler_reset :: proc(pResampler: ^resampler) -> result --- } @@ -421,7 +421,7 @@ foreign lib { /* Copies a channel map. - Both input and output channel map buffers must have a capacity of at at least `channels`. + Both input and output channel map buffers must have a capacity of at least `channels`. */ channel_map_copy :: proc(pOut: [^]channel, pIn: [^]channel, channels: u32) --- diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index f1fa279ac..e2f33b8e7 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -71,7 +71,7 @@ decoder :: struct { pInputCache: rawptr, /* In input format. Can be null if it's not needed. */ inputCacheCap: u64, /* The capacity of the input cache. */ inputCacheConsumed: u64, /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ - inputCacheRemaining: u64, /* The number of valid frames remaining in the cahce. */ + inputCacheRemaining: u64, /* The number of valid frames remaining in the cache. */ allocationCallbacks: allocation_callbacks, data: struct #raw_union { vfs: struct { @@ -111,7 +111,7 @@ foreign lib { decoder_read_pcm_frames :: proc(pDecoder: ^decoder, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- /* - Seeks to a PCM frame based on it's absolute index. + Seeks to a PCM frame based on its absolute index. This is not thread safe without your own synchronization. */ diff --git a/vendor/miniaudio/device_io_procs.odin b/vendor/miniaudio/device_io_procs.odin index 21ac1afd7..a14de807c 100644 --- a/vendor/miniaudio/device_io_procs.odin +++ b/vendor/miniaudio/device_io_procs.odin @@ -12,6 +12,8 @@ foreign lib { device_job_thread_uninit :: proc(pJobThread: ^device_job_thread, pAllocationCallbacks: ^allocation_callbacks) --- device_job_thread_post :: proc(pJobThread: ^device_job_thread, pJob: ^job) -> result --- device_job_thread_next :: proc(pJobThread: ^device_job_thread, pJob: ^job) -> result --- + + device_id_equal :: proc(pA: ^device_id, pB: ^device_id) -> b32 --- /* Initializes a `ma_context_config` object. @@ -370,6 +372,9 @@ foreign lib { This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos` parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback. + Note that this only retrieves the ID and name/description of the device. The reason for only retrieving basic information is that it would otherwise require + opening the backend device in order to probe it for more detailed information which can be inefficient. Consider using `ma_context_get_device_info()` for this, + but don't call it from within the enumeration callback. Parameters ---------- @@ -411,7 +416,7 @@ foreign lib { See Also -------- - ma_context_get_devices() + ma_context_enumerate_devices() */ context_get_devices :: proc(pContext: ^context_type, ppPlaybackDeviceInfos: ^[^]device_info, pPlaybackDeviceCount: ^u32, ppCaptureDeviceInfos: ^[^]device_info, pCaptureDeviceCount: ^u32) -> result --- @@ -550,7 +555,7 @@ foreign lib { playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the device is done via a callback which is fired by miniaudio at periodic time intervals. - The frequency at which data is delivered to and from a device depends on the size of it's period. The size of the period can be defined in terms of PCM frames + The frequency at which data is delivered to and from a device depends on the size of its period. The size of the period can be defined in terms of PCM frames or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple @@ -624,7 +629,7 @@ foreign lib { performanceProfile A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or - `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. + `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value. noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of @@ -664,7 +669,7 @@ foreign lib { A pointer that will passed to callbacks in pBackendVTable. resampling.linear.lpfOrder - The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher + The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. @@ -741,6 +746,9 @@ foreign lib { pulse.pStreamNameCapture PulseAudio only. Sets the stream name for capture. + pulse.channelMap + PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF. + coreaudio.allowNominalSampleRateChange Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate @@ -914,7 +922,7 @@ foreign lib { Remarks ------- - You only need to use this function if you want to configure the context differently to it's defaults. You should never use this function if you want to manage + You only need to use this function if you want to configure the context differently to its defaults. You should never use this function if you want to manage your own context. See the documentation for `ma_context_init()` for information on the different context configuration options. diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index b52a3f423..9d64602f8 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -427,6 +427,7 @@ device_config :: struct { pulse: struct { pStreamNamePlayback: cstring, pStreamNameCapture: cstring, + channelMap: i32, }, coreaudio: struct { allowNominalSampleRateChange: b32, /* Desktop only. When enabled, allows changing of the sample rate at the operating system level. */ @@ -443,6 +444,7 @@ device_config :: struct { allowedCapturePolicy: aaudio_allowed_capture_policy, noAutoStartAfterReroute: b32, enableCompatibilityWorkarounds: b32, + allowSetBufferCapacity: b32, }, } @@ -514,7 +516,7 @@ and on output returns detailed information about the device in `ma_device_info`. case when the device ID is NULL, in which case information about the default device needs to be retrieved. Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created. -This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a +This is a little bit more complicated than initialization of the context due to its more complicated configuration. When initializing a device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input, the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to the requested format. The conversion between the format requested by the application and the device's native format will be handled @@ -535,10 +537,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and `onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the -backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback. +backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within its callback. This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback. -If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback +If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been @@ -575,6 +577,9 @@ context_config :: struct { threadStackSize: c.size_t, pUserData: rawptr, allocationCallbacks: allocation_callbacks, + dsound: struct { + hWnd: handle, /* HWND. Optional window handle to pass into SetCooperativeLevel(). Will default to the foreground window, and if that fails, the desktop window. */ + }, alsa: struct { useVerboseDeviceEnumeration: b32, }, @@ -649,6 +654,7 @@ context_type :: struct { } when SUPPORT_WASAPI else struct {}), dsound: (struct { + hWnd: handle, /* Can be null. */ hDSoundDLL: handle, DirectSoundCreate: proc "system" (), DirectSoundEnumerateA: proc "system" (), @@ -1195,6 +1201,7 @@ device :: struct { aaudio: (struct { /*AAudioStream**/ pStreamPlayback: rawptr, /*AAudioStream**/ pStreamCapture: rawptr, + rerouteLock: mutex, usage: aaudio_usage, contentType: aaudio_content_type, inputPreset: aaudio_input_preset, diff --git a/vendor/miniaudio/doc.odin b/vendor/miniaudio/doc.odin index 33c613ae4..ff7924b89 100644 --- a/vendor/miniaudio/doc.odin +++ b/vendor/miniaudio/doc.odin @@ -295,7 +295,7 @@ avoids the same sound being loaded multiple times. The node graph is used for mixing and effect processing. The idea is that you connect a number of nodes into the graph by connecting each node's outputs to another node's inputs. Each node can -implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +implement its own effect. By chaining nodes together, advanced mixing and effect processing can be achieved. The engine encapsulates both the resource manager and the node graph to create a simple, easy to @@ -400,7 +400,7 @@ the be started and/or stopped at a specific time. This can be done with the foll ``` The start/stop time needs to be specified based on the absolute timer which is controlled by the -engine. The current global time time in PCM frames can be retrieved with +engine. The current global time in PCM frames can be retrieved with `ma_engine_get_time_in_pcm_frames()`. The engine's global time can be changed with `ma_engine_set_time_in_pcm_frames()` for synchronization purposes if required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before anything will play: @@ -432,11 +432,11 @@ Sounds and sound groups are nodes in the engine's node graph and can be plugged API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex effect chains. -A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +A sound can have its volume changed with `ma_sound_set_volume()`. If you prefer decibel volume control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know -a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +a sound will never have its pitch changed with `ma_sound_set_pitch()` or via the doppler effect, you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. By default, sounds and sound groups have spatialization enabled. If you don't ever want to @@ -485,21 +485,12 @@ link the relevant frameworks but should compile cleanly out of the box with Xcod through the command line requires linking to `-lpthread` and `-lm`. Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's -notarization process. To fix this there are two options. The first is to use the -`MA_NO_RUNTIME_LINKING` option, like so: - - ```c - #ifdef __APPLE__ - #define MA_NO_RUNTIME_LINKING - #endif - #define MINIAUDIO_IMPLEMENTATION - #include "miniaudio.h" - ``` - -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. -If you get errors about AudioToolbox, try with `-framework AudioUnit` instead. You may get this when -using older versions of iOS. Alternatively, if you would rather keep using runtime linking you can -add the following to your entitlements.xcent file: +notarization process. To fix this there are two options. The first is to compile with +`-DMA_NO_RUNTIME_LINKING` which in turn will require linking with +`-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. If you get errors about +AudioToolbox, try with `-framework AudioUnit` instead. You may get this when using older versions +of iOS. Alternatively, if you would rather keep using runtime linking you can add the following to +your entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -557,7 +548,7 @@ To run locally, you'll need to use emrun: 2.7. Build Options ------------------ -`#define` these options before including miniaudio.h. +`#define` these options before including miniaudio.c, or pass them as compiler flags: +----------------------------------+--------------------------------------------------------------------+ | Option | Description | @@ -588,6 +579,8 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_WEBAUDIO | Disables the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_CUSTOM | Disables support for custom backends. | + +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NULL | Disables the null backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_ONLY_SPECIFIC_BACKENDS | Disables all backends by default and requires `MA_ENABLE_*` to | @@ -632,6 +625,9 @@ To run locally, you'll need to use emrun: | MA_ENABLE_WEBAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_CUSTOM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable custom backends. | + +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_NULL | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the null backend. | +----------------------------------+--------------------------------------------------------------------+ @@ -695,11 +691,30 @@ To run locally, you'll need to use emrun: | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ + | MA_USE_STDINT | (Pass this in a compiler flag. Do not #define this before | + | | miniaudio.c) Forces the use of stdint.h for sized types. | + +----------------------------------+--------------------------------------------------------------------+ | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ + | MA_FORCE_UWP | Windows only. Affects only the WASAPI backend. Will force the | + | | WASAPI backend to use the UWP code path instead of the regular | + | | desktop path. This is normally auto-detected and should rarely be | + | | needed to be used explicitly, but can be useful for debugging. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ON_THREAD_ENTRY | Defines some code that will be executed as soon as an internal | + | | miniaudio-managed thread is created. This will be the first thing | + | | to be executed by the thread entry point. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ON_THREAD_EXIT | Defines some code that will be executed from the entry point of an | + | | internal miniaudio-managed thread upon exit. This will be the last | + | | thing to be executed before the thread's entry point exits. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_THREAD_DEFAULT_STACK_SIZE | If set, specifies the default stack size used by miniaudio-managed | + | | threads. | + +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ @@ -1311,7 +1326,7 @@ only works for sounds that were initialized with `ma_sound_init_from_file()` and When you initialize a sound, if you specify a sound group the sound will be attached to that group automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. -If you would instead rather leave the sound unattached by default, you can can specify the +If you would instead rather leave the sound unattached by default, you can specify the `MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node graph. @@ -1688,6 +1703,7 @@ combination of the following flags: MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING ``` When no flags are specified (set to 0), the sound will be fully loaded into memory, but not @@ -1708,6 +1724,14 @@ can instead stream audio data which you can do by specifying the second pages. When a new page needs to be decoded, a job will be posted to the job queue and then subsequently processed in a job thread. +The `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flag can be used so that the sound will loop +when it reaches the end by default. It's recommended you use this flag when you want to have a +looping streaming sound. If you try loading a very short sound as a stream, you will get a glitch. +This is because the resource manager needs to pre-fill the initial buffer at initialization time, +and if you don't specify the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flag, the resource +manager will assume the sound is not looping and will stop filling the buffer when it reaches the +end, therefore resulting in a discontinuous buffer. + For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be @@ -1722,7 +1746,7 @@ actual file paths. When `ma_resource_manager_data_source_init()` is called (with `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these explicitly registered data buffers and, if found, will use it as the backing data for the data source. Note that the resource manager does *not* make a copy of this data so it is up to the -caller to ensure the pointer stays valid for it's lifetime. Use +caller to ensure the pointer stays valid for its lifetime. Use `ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use `ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` @@ -2033,7 +2057,7 @@ In the above graph, it starts with two data sources whose outputs are attached t splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter performs it's processing routine and produces two outputs which is simply a duplication of the input stream. One output is attached to a low pass filter, whereas the other output is attached to -a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +a echo/delay. The outputs of the low pass filter and the echo are attached to the endpoint, and since they're both connected to the same input bus, they'll be mixed. Each input bus must be configured to accept the same number of channels, but the number of channels @@ -2074,7 +2098,7 @@ data from the graph: ``` When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in -data from it's input attachments, which in turn recursively pull in data from their inputs, and so +data from its input attachments, which in turn recursively pull in data from their inputs, and so on. At the start of the graph there will be some kind of data source node which will have zero inputs and will instead read directly from a data source. The base nodes don't literally need to read from a `ma_data_source` object, but they will always have some kind of underlying object that @@ -2320,7 +2344,7 @@ You can start and stop a node with the following: By default the node is in a started state, but since it won't be connected to anything won't actually be invoked by the node graph until it's connected. When you stop a node, data will not be -read from any of it's input connections. You can use this property to stop a group of sounds +read from any of its input connections. You can use this property to stop a group of sounds atomically. You can configure the initial state of a node in it's config: @@ -2413,29 +2437,29 @@ audio thread is finished so that control is not handed back to the caller thereb chance to free the node's memory. When the audio thread is processing a node, it does so by reading from each of the output buses of -the node. In order for a node to process data for one of it's output buses, it needs to read from -each of it's input buses, and so on an so forth. It follows that once all output buses of a node +the node. In order for a node to process data for one of its output buses, it needs to read from +each of its input buses, and so on an so forth. It follows that once all output buses of a node are detached, the node as a whole will be disconnected and no further processing will occur unless it's output buses are reattached, which won't be happening when the node is being uninitialized. By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output -nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +nodes, followed by each of the attachments to each of its input nodes, and then do any final clean up. With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as it takes to process the output bus being detached. This will happen if it's called at just the wrong moment where the audio thread has just iterated it and has just started processing. The caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which -includes the cost of recursively processing it's inputs. This is the biggest compromise made with -the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +includes the cost of recursively processing its inputs. This is the biggest compromise made with +the approach taken by miniaudio for its lock-free processing system. The cost of detaching nodes earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass detachments, detach starting from the lowest level nodes and work your way towards the final endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not running, detachment will be fast and detachment in any order will be the same. The reason nodes need to wait for their input attachments to complete is due to the potential for desyncs between -data sources. If the node was to terminate processing mid way through processing it's inputs, +data sources. If the node was to terminate processing mid way through processing its inputs, there's a chance that some of the underlying data sources will have been read, but then others not. That will then result in a potential desynchronization when detaching and reattaching higher-level nodes. A possible solution to this is to have an option when detaching to terminate processing @@ -2806,7 +2830,7 @@ weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a -`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +`ma_standard_channel_map` enum as its first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -2892,7 +2916,7 @@ like the following: ma_resample_algorithm_linear); ma_resampler resampler; - ma_result result = ma_resampler_init(&config, &resampler); + ma_result result = ma_resampler_init(&config, NULL, &resampler); if (result != MA_SUCCESS) { // An error occurred... } @@ -3134,7 +3158,7 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ```c ma_biquad_config config = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2); - ma_result result = ma_biquad_init(&config, &biquad); + ma_result result = ma_biquad_init(&config, NULL, &biquad); if (result != MA_SUCCESS) { // Error. } diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index a06e6c62c..e5364d782 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -18,7 +18,8 @@ sound_flag :: enum c.int { ASYNC = 2, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ WAIT_INIT = 3, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ UNKNOWN_LENGTH = 4, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ - + LOOPING = 5, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING */ + /* ma_sound specific flags. */ NO_DEFAULT_ATTACHMENT = 12, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ NO_PITCH = 13, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ @@ -51,7 +52,7 @@ engine_node_config :: struct { /* Base node object for both ma_sound and ma_sound_group. */ engine_node :: struct { - baseNode: node_base, /* Must be the first member for compatiblity with the ma_node API. */ + baseNode: node_base, /* Must be the first member for compatibility with the ma_node API. */ pEngine: ^engine, /* A pointer to the engine. Set based on the value from the config. */ sampleRate: u32, /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ volumeSmoothTimeInPCMFrames: u32, @@ -113,7 +114,6 @@ sound_config :: struct { rangeEndInPCMFrames: u64, loopPointBegInPCMFrames: u64, loopPointEndInPCMFrames: u64, - isLooping: b32, endCallback: sound_end_proc, /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ pEndCallbackUserData: rawptr, @@ -121,6 +121,8 @@ sound_config :: struct { initNotifications: resource_manager_pipeline_notifications, pDoneFence: ^fence, /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */ + + isLooping: b32, /* Deprecated. Use the MA_SOUND_FLAG_LOOPING in `flags` instead. */ } sound :: struct { @@ -226,6 +228,7 @@ foreign lib { sound_is_looping :: proc(pSound: ^sound) -> b32 --- sound_at_end :: proc(pSound: ^sound) -> b32 --- sound_seek_to_pcm_frame :: proc(pSound: ^sound, frameIndex: u64) -> result --- /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ + sound_seek_to_second :: proc(pSound: ^sound, seekPointInSeconds: f32) -> result --- /* Abstraction to ma_sound_seek_to_pcm_frame() */ sound_get_data_format :: proc(pSound: ^sound, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: ^channel, channelMapCap: c.size_t) -> result --- sound_get_cursor_in_pcm_frames :: proc(pSound: ^sound, pCursor: ^u64) -> result --- sound_get_length_in_pcm_frames :: proc(pSound: ^sound, pLength: ^u64) -> result --- @@ -323,6 +326,7 @@ engine_config :: struct { gainSmoothTimeInMilliseconds: u32, /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ defaultVolumeSmoothTimeInPCMFrames: u32, /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */ + preMixStackSizeInBytes: u32, /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */ allocationCallbacks: allocation_callbacks, noAutoStart: b32, /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ @@ -344,7 +348,7 @@ engine :: struct { allocationCallbacks: allocation_callbacks, ownsResourceManager: b8, ownsDevice: b8, - inlinedSoundLock: spinlock, /* For synchronizing access so the inlined sound list. */ + inlinedSoundLock: spinlock, /* For synchronizing access to the inlined sound list. */ pInlinedSoundHead: ^sound_inlined, /* The first inlined sound. Inlined sounds are tracked in a linked list. */ inlinedSoundCount: u32, /*atomic*/ /* The total number of allocated inlined sound objects. Used for debugging. */ gainSmoothTimeInFrames: u32, /* The number of frames to interpolate the gain of spatialized sounds across. */ diff --git a/vendor/miniaudio/lib/miniaudio.lib b/vendor/miniaudio/lib/miniaudio.lib index d339c746e..c9ee60979 100644 Binary files a/vendor/miniaudio/lib/miniaudio.lib and b/vendor/miniaudio/lib/miniaudio.lib differ diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index 610ada7a8..496d566f4 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -19,6 +19,13 @@ MAX_NODE_LOCAL_BUS_COUNT :: 2 /* Use this when the bus count is determined by the node instance rather than the vtable. */ NODE_BUS_COUNT_UNKNOWN :: 255 +/* For some internal memory management of ma_node_graph. */ +stack :: struct { + offset: uint, + sizeInBytes: uint, + _data: [1]byte, +} + node :: struct {} /* Node flags. */ @@ -53,7 +60,7 @@ node_vtable :: struct { onProcess: proc "c" (pNode: ^node, ppFramesIn: ^[^]f32, pFrameCountIn: ^u32, ppFramesOut: ^[^]f32, pFrameCountOut: ^u32), /* - A callback for retrieving the number of a input frames that are required to output the + A callback for retrieving the number of input frames that are required to output the specified number of output frames. You would only want to implement this when the node performs resampling. This is optional, even for nodes that perform resampling, but it does offer a small reduction in latency as it allows miniaudio to calculate the exact number of input frames @@ -134,8 +141,12 @@ node_input_bus :: struct { node_base :: struct { /* These variables are set once at startup. */ - pNodeGraph: ^node_graph, /* The graph this node belongs to. */ + pNodeGraph: ^node_graph, /* The graph this node belongs to. */ vtable: ^node_vtable, + inputBusCount: u32, + outputBusCount: u32, + pInputBuses: [^]node_input_bus `fmt:"v,inputBusCount"`, + pOutputBuses: [^]node_output_bus `fmt:"v,outputBusCount"`, pCachedData: [^]f32, /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ cachedDataCapInFramesPerBus: u16, /* The capacity of the input data cache in frames, per bus. */ @@ -148,10 +159,6 @@ node_base :: struct { state: node_state, /*atomic*/ /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ stateTimes: [2]u64, /*atomic*/ /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ localTime: u64, /*atomic*/ /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ - inputBusCount: u32, - outputBusCount: u32, - pInputBuses: [^]node_input_bus, - pOutputBuses: [^]node_output_bus, /* Memory management. */ _inputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_input_bus, @@ -189,18 +196,25 @@ foreign lib { } node_graph_config :: struct { - channels: u32, - nodeCacheCapInFrames: u16, + channels: u32, + processingSizeInFrames: u32, /* This is the preferred processing size for node processing callbacks unless overridden by a node itself. Can be 0 in which case it will be based on the frame count passed into ma_node_graph_read_pcm_frames(), but will not be well defined. */ + preMixStackSizeInBytes: uint, /* Defaults to 512KB per channel. Reducing this will save memory, but the depth of your node graph will be more restricted. */ } node_graph :: struct { /* Immutable. */ base: node_base, /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ endpoint: node_base, /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ - nodeCacheCapInFrames: u16, + + pProcessingCache: [^]f32, /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */ + processingCacheFramesRemaining: u32, + processingSizeInFrames: u32, /* Read and written by multiple threads. */ isReading: b32, /*atomic*/ + + /* Modified only by the audio thread. */ + pPreMixStack: ^stack, } @(default_calling_convention="c", link_prefix="ma_") diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index 495a02c5d..4ef5778a9 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -16,6 +16,7 @@ resource_manager_data_source_flag :: enum c.int { ASYNC = 2, /* When set, the resource manager will load the data source asynchronously. */ WAIT_INIT = 3, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ UNKNOWN_LENGTH = 4, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ + LOOPING = 5, /* When set, configures the data source to loop by default. */ } resource_manager_data_source_flags :: bit_set[resource_manager_data_source_flag; u32] @@ -79,8 +80,8 @@ resource_manager_data_source_config :: struct { rangeEndInPCMFrames: u64, loopPointBegInPCMFrames: u64, loopPointEndInPCMFrames: u64, - isLooping: b32, flags: u32, + isLooping: b32, /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */ } resource_manager_data_supply_type :: enum c.int { diff --git a/vendor/miniaudio/src/miniaudio.h b/vendor/miniaudio/src/miniaudio.h index 47332e11a..c74bebeb3 100644 --- a/vendor/miniaudio/src/miniaudio.h +++ b/vendor/miniaudio/src/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.21 - 2023-11-15 +miniaudio - v0.11.22 - 2025-02-24 David Reid - mackron@gmail.com @@ -12,15 +12,18 @@ GitHub: https://github.com/mackron/miniaudio /* 1. Introduction =============== -miniaudio is a single file library for audio playback and capture. To use it, do the following in -one .c file: +To use miniaudio, include "miniaudio.h": ```c - #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" ``` -You can do `#include "miniaudio.h"` in other parts of the program just like any other header. +The implementation is contained in "miniaudio.c". Just compile this like any other source file. You +can include miniaudio.c if you want to compile your project as a single translation unit: + + ```c + #include "miniaudio.c" + ``` miniaudio includes both low level and high level APIs. The low level API is good for those who want to do all of their mixing themselves and only require a light weight interface to the underlying @@ -293,7 +296,7 @@ avoids the same sound being loaded multiple times. The node graph is used for mixing and effect processing. The idea is that you connect a number of nodes into the graph by connecting each node's outputs to another node's inputs. Each node can -implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +implement its own effect. By chaining nodes together, advanced mixing and effect processing can be achieved. The engine encapsulates both the resource manager and the node graph to create a simple, easy to @@ -398,7 +401,7 @@ the be started and/or stopped at a specific time. This can be done with the foll ``` The start/stop time needs to be specified based on the absolute timer which is controlled by the -engine. The current global time time in PCM frames can be retrieved with +engine. The current global time in PCM frames can be retrieved with `ma_engine_get_time_in_pcm_frames()`. The engine's global time can be changed with `ma_engine_set_time_in_pcm_frames()` for synchronization purposes if required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before anything will play: @@ -430,11 +433,11 @@ Sounds and sound groups are nodes in the engine's node graph and can be plugged API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex effect chains. -A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +A sound can have its volume changed with `ma_sound_set_volume()`. If you prefer decibel volume control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know -a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +a sound will never have its pitch changed with `ma_sound_set_pitch()` or via the doppler effect, you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. By default, sounds and sound groups have spatialization enabled. If you don't ever want to @@ -483,21 +486,12 @@ link the relevant frameworks but should compile cleanly out of the box with Xcod through the command line requires linking to `-lpthread` and `-lm`. Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's -notarization process. To fix this there are two options. The first is to use the -`MA_NO_RUNTIME_LINKING` option, like so: - - ```c - #ifdef __APPLE__ - #define MA_NO_RUNTIME_LINKING - #endif - #define MINIAUDIO_IMPLEMENTATION - #include "miniaudio.h" - ``` - -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. -If you get errors about AudioToolbox, try with `-framework AudioUnit` instead. You may get this when -using older versions of iOS. Alternatively, if you would rather keep using runtime linking you can -add the following to your entitlements.xcent file: +notarization process. To fix this there are two options. The first is to compile with +`-DMA_NO_RUNTIME_LINKING` which in turn will require linking with +`-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. If you get errors about +AudioToolbox, try with `-framework AudioUnit` instead. You may get this when using older versions +of iOS. Alternatively, if you would rather keep using runtime linking you can add the following to +your entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -555,7 +549,7 @@ To run locally, you'll need to use emrun: 2.7. Build Options ------------------ -`#define` these options before including miniaudio.h. +`#define` these options before including miniaudio.c, or pass them as compiler flags: +----------------------------------+--------------------------------------------------------------------+ | Option | Description | @@ -586,6 +580,8 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_WEBAUDIO | Disables the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_CUSTOM | Disables support for custom backends. | + +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NULL | Disables the null backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_ONLY_SPECIFIC_BACKENDS | Disables all backends by default and requires `MA_ENABLE_*` to | @@ -630,6 +626,9 @@ To run locally, you'll need to use emrun: | MA_ENABLE_WEBAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_CUSTOM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable custom backends. | + +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_NULL | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the null backend. | +----------------------------------+--------------------------------------------------------------------+ @@ -693,11 +692,30 @@ To run locally, you'll need to use emrun: | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ + | MA_USE_STDINT | (Pass this in as a compiler flag. Do not `#define` this before | + | | miniaudio.c) Forces the use of stdint.h for sized types. | + +----------------------------------+--------------------------------------------------------------------+ | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ + | MA_FORCE_UWP | Windows only. Affects only the WASAPI backend. Will force the | + | | WASAPI backend to use the UWP code path instead of the regular | + | | desktop path. This is normally auto-detected and should rarely be | + | | needed to be used explicitly, but can be useful for debugging. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ON_THREAD_ENTRY | Defines some code that will be executed as soon as an internal | + | | miniaudio-managed thread is created. This will be the first thing | + | | to be executed by the thread entry point. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ON_THREAD_EXIT | Defines some code that will be executed from the entry point of an | + | | internal miniaudio-managed thread upon exit. This will be the last | + | | thing to be executed before the thread's entry point exits. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_THREAD_DEFAULT_STACK_SIZE | If set, specifies the default stack size used by miniaudio-managed | + | | threads. | + +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ @@ -1309,7 +1327,7 @@ only works for sounds that were initialized with `ma_sound_init_from_file()` and When you initialize a sound, if you specify a sound group the sound will be attached to that group automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. -If you would instead rather leave the sound unattached by default, you can can specify the +If you would instead rather leave the sound unattached by default, you can specify the `MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node graph. @@ -1686,6 +1704,7 @@ combination of the following flags: MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING ``` When no flags are specified (set to 0), the sound will be fully loaded into memory, but not @@ -1706,6 +1725,14 @@ can instead stream audio data which you can do by specifying the second pages. When a new page needs to be decoded, a job will be posted to the job queue and then subsequently processed in a job thread. +The `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flag can be used so that the sound will loop +when it reaches the end by default. It's recommended you use this flag when you want to have a +looping streaming sound. If you try loading a very short sound as a stream, you will get a glitch. +This is because the resource manager needs to pre-fill the initial buffer at initialization time, +and if you don't specify the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flag, the resource +manager will assume the sound is not looping and will stop filling the buffer when it reaches the +end, therefore resulting in a discontinuous buffer. + For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be @@ -1720,7 +1747,7 @@ actual file paths. When `ma_resource_manager_data_source_init()` is called (with `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these explicitly registered data buffers and, if found, will use it as the backing data for the data source. Note that the resource manager does *not* make a copy of this data so it is up to the -caller to ensure the pointer stays valid for it's lifetime. Use +caller to ensure the pointer stays valid for its lifetime. Use `ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use `ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` @@ -2031,7 +2058,7 @@ In the above graph, it starts with two data sources whose outputs are attached t splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter performs it's processing routine and produces two outputs which is simply a duplication of the input stream. One output is attached to a low pass filter, whereas the other output is attached to -a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +a echo/delay. The outputs of the low pass filter and the echo are attached to the endpoint, and since they're both connected to the same input bus, they'll be mixed. Each input bus must be configured to accept the same number of channels, but the number of channels @@ -2072,7 +2099,7 @@ data from the graph: ``` When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in -data from it's input attachments, which in turn recursively pull in data from their inputs, and so +data from its input attachments, which in turn recursively pull in data from their inputs, and so on. At the start of the graph there will be some kind of data source node which will have zero inputs and will instead read directly from a data source. The base nodes don't literally need to read from a `ma_data_source` object, but they will always have some kind of underlying object that @@ -2318,7 +2345,7 @@ You can start and stop a node with the following: By default the node is in a started state, but since it won't be connected to anything won't actually be invoked by the node graph until it's connected. When you stop a node, data will not be -read from any of it's input connections. You can use this property to stop a group of sounds +read from any of its input connections. You can use this property to stop a group of sounds atomically. You can configure the initial state of a node in it's config: @@ -2411,29 +2438,29 @@ audio thread is finished so that control is not handed back to the caller thereb chance to free the node's memory. When the audio thread is processing a node, it does so by reading from each of the output buses of -the node. In order for a node to process data for one of it's output buses, it needs to read from -each of it's input buses, and so on an so forth. It follows that once all output buses of a node +the node. In order for a node to process data for one of its output buses, it needs to read from +each of its input buses, and so on an so forth. It follows that once all output buses of a node are detached, the node as a whole will be disconnected and no further processing will occur unless it's output buses are reattached, which won't be happening when the node is being uninitialized. By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output -nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +nodes, followed by each of the attachments to each of its input nodes, and then do any final clean up. With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as it takes to process the output bus being detached. This will happen if it's called at just the wrong moment where the audio thread has just iterated it and has just started processing. The caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which -includes the cost of recursively processing it's inputs. This is the biggest compromise made with -the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +includes the cost of recursively processing its inputs. This is the biggest compromise made with +the approach taken by miniaudio for its lock-free processing system. The cost of detaching nodes earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass detachments, detach starting from the lowest level nodes and work your way towards the final endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not running, detachment will be fast and detachment in any order will be the same. The reason nodes need to wait for their input attachments to complete is due to the potential for desyncs between -data sources. If the node was to terminate processing mid way through processing it's inputs, +data sources. If the node was to terminate processing mid way through processing its inputs, there's a chance that some of the underlying data sources will have been read, but then others not. That will then result in a potential desynchronization when detaching and reattaching higher-level nodes. A possible solution to this is to have an option when detaching to terminate processing @@ -2804,7 +2831,7 @@ weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a -`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +`ma_standard_channel_map` enum as its first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -2890,7 +2917,7 @@ like the following: ma_resample_algorithm_linear); ma_resampler resampler; - ma_result result = ma_resampler_init(&config, &resampler); + ma_result result = ma_resampler_init(&config, NULL, &resampler); if (result != MA_SUCCESS) { // An error occurred... } @@ -3132,7 +3159,7 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ```c ma_biquad_config config = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2); - ma_result result = ma_biquad_init(&config, &biquad); + ma_result result = ma_biquad_init(&config, NULL, &biquad); if (result != MA_SUCCESS) { // Error. } @@ -3723,7 +3750,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 11 -#define MA_VERSION_REVISION 21 +#define MA_VERSION_REVISION 22 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -3740,8 +3767,7 @@ extern "C" { #endif - -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) || defined(__ppc64__) #define MA_SIZEOF_PTR 8 #else #define MA_SIZEOF_PTR 4 @@ -3805,7 +3831,7 @@ typedef void* ma_handle; typedef void* ma_ptr; /* -ma_proc is annoying because when compiling with GCC we get pendantic warnings about converting +ma_proc is annoying because when compiling with GCC we get pedantic warnings about converting between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get warning C4191 about "type cast between incompatible function types". To work around this I'm going to use a different data type depending on the compiler. @@ -3999,7 +4025,7 @@ Special wchar_t type to ensure any structures in the public sections that refere consistent size across all platforms. On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use -wchar_t for it's IDs, we need a special explicitly sized wchar type that is always 2 bytes on all +wchar_t for its IDs, we need a special explicitly sized wchar type that is always 2 bytes on all platforms. */ #if !defined(MA_POSIX) && defined(MA_WIN32) @@ -4025,7 +4051,7 @@ MA_LOG_LEVEL_INFO callback. MA_LOG_LEVEL_WARNING - Warnings. You should enable this in you development builds and action them when encounted. These + Warnings. You should enable this in you development builds and action them when encountered. These logs usually indicate a potential problem or misconfiguration, but still allow you to keep running. This will never be called from within the data callback. @@ -5457,7 +5483,7 @@ input frames. MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /* -Resets the resampler's timer and clears it's internal cache. +Resets the resampler's timer and clears its internal cache. */ MA_API ma_result ma_resampler_reset(ma_resampler* pResampler); @@ -5678,7 +5704,7 @@ MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannel /* Copies a channel map. -Both input and output channel map buffers must have a capacity of at at least `channels`. +Both input and output channel map buffers must have a capacity of at least `channels`. */ MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); @@ -5817,6 +5843,8 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource); MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); +MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked); /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */ +MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds); /* Abstraction to ma_data_source_seek_to_pcm_frame() */ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ @@ -6182,6 +6210,12 @@ MA_API ma_result ma_event_wait(ma_event* pEvent); Signals the specified auto-reset event. */ MA_API ma_result ma_event_signal(ma_event* pEvent); + + +MA_API ma_result ma_semaphore_init(int initialValue, ma_semaphore* pSemaphore); +MA_API void ma_semaphore_uninit(ma_semaphore* pSemaphore); +MA_API ma_result ma_semaphore_wait(ma_semaphore* pSemaphore); +MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore); #endif /* MA_NO_THREADING */ @@ -6273,7 +6307,7 @@ Job Queue /* Slot Allocator -------------- -The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocate an index that can be used as the insertion point for an object. Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. @@ -7006,6 +7040,8 @@ typedef union int nullbackend; /* The null backend uses an integer for device IDs. */ } ma_device_id; +MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB); + typedef struct ma_context_config ma_context_config; typedef struct ma_device_config ma_device_config; @@ -7093,6 +7129,7 @@ struct ma_device_config { const char* pStreamNamePlayback; const char* pStreamNameCapture; + int channelMap; } pulse; struct { @@ -7112,6 +7149,7 @@ struct ma_device_config ma_aaudio_allowed_capture_policy allowedCapturePolicy; ma_bool32 noAutoStartAfterReroute; ma_bool32 enableCompatibilityWorkarounds; + ma_bool32 allowSetBufferCapacity; } aaudio; }; @@ -7184,7 +7222,7 @@ and on output returns detailed information about the device in `ma_device_info`. case when the device ID is NULL, in which case information about the default device needs to be retrieved. Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created. -This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a +This is a little bit more complicated than initialization of the context due to its more complicated configuration. When initializing a device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input, the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to the requested format. The conversion between the format requested by the application and the device's native format will be handled @@ -7205,10 +7243,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and `onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the -backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback. +backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within its callback. This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback. -If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback +If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been @@ -7248,6 +7286,10 @@ struct ma_context_config void* pUserData; ma_allocation_callbacks allocationCallbacks; struct + { + ma_handle hWnd; /* HWND. Optional window handle to pass into SetCooperativeLevel(). Will default to the foreground window, and if that fails, the desktop window. */ + } dsound; + struct { ma_bool32 useVerboseDeviceEnumeration; } alsa; @@ -7336,6 +7378,7 @@ struct ma_context #ifdef MA_SUPPORT_DSOUND struct { + ma_handle hWnd; /* Can be null. */ ma_handle hDSoundDLL; ma_proc DirectSoundCreate; ma_proc DirectSoundEnumerateA; @@ -7942,6 +7985,7 @@ struct ma_device { /*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamCapture; + ma_mutex rerouteLock; ma_aaudio_usage usage; ma_aaudio_content_type contentType; ma_aaudio_input_preset inputPreset; @@ -8365,6 +8409,10 @@ Retrieves basic information about every active playback and/or capture device. This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos` parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback. +Note that this only retrieves the ID and name/description of the device. The reason for only retrieving basic information is that it would otherwise require +opening the backend device in order to probe it for more detailed information which can be inefficient. Consider using `ma_context_get_device_info()` for this, +but don't call it from within the enumeration callback. + Parameters ---------- @@ -8406,7 +8454,7 @@ The returned pointers will become invalid upon the next call this this function, See Also -------- -ma_context_get_devices() +ma_context_enumerate_devices() */ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount); @@ -8545,7 +8593,7 @@ from a microphone. Whether or not you should send or receive data from the devic playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the device is done via a callback which is fired by miniaudio at periodic time intervals. -The frequency at which data is delivered to and from a device depends on the size of it's period. The size of the period can be defined in terms of PCM frames +The frequency at which data is delivered to and from a device depends on the size of its period. The size of the period can be defined in terms of PCM frames or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple @@ -8619,7 +8667,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c performanceProfile A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or - `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. + `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value. noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of @@ -8659,7 +8707,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c A pointer that will passed to callbacks in pBackendVTable. resampling.linear.lpfOrder - The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher + The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. @@ -8736,6 +8784,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c pulse.pStreamNameCapture PulseAudio only. Sets the stream name for capture. + pulse.channelMap + PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF. + coreaudio.allowNominalSampleRateChange Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate @@ -8909,7 +8960,7 @@ Unsafe. It is not safe to call this inside any callback. Remarks ------- -You only need to use this function if you want to configure the context differently to it's defaults. You should never use this function if you want to manage +You only need to use this function if you want to configure the context differently to its defaults. You should never use this function if you want to manage your own context. See the documentation for `ma_context_init()` for information on the different context configuration options. @@ -9674,7 +9725,7 @@ Utilities ************************************************************************************************************************************************************/ /* -Calculates a buffer size in milliseconds from the specified number of frames and sample rate. +Calculates a buffer size in milliseconds (rounded up) from the specified number of frames and sample rate. */ MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate); @@ -9931,7 +9982,7 @@ struct ma_decoder void* pInputCache; /* In input format. Can be null if it's not needed. */ ma_uint64 inputCacheCap; /* The capacity of the input cache. */ ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ - ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ + ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */ ma_allocation_callbacks allocationCallbacks; union { @@ -9972,7 +10023,7 @@ This is not thread safe without your own synchronization. MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* -Seeks to a PCM frame based on it's absolute index. +Seeks to a PCM frame based on its absolute index. This is not thread safe without your own synchronization. */ @@ -10235,7 +10286,8 @@ typedef enum MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ - MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING = 0x00000020 /* When set, configures the data source to loop by default. */ } ma_resource_manager_data_source_flags; @@ -10303,8 +10355,8 @@ typedef struct ma_uint64 rangeEndInPCMFrames; ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointEndInPCMFrames; - ma_bool32 isLooping; ma_uint32 flags; + ma_bool32 isLooping; /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */ } ma_resource_manager_data_source_config; MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); @@ -10547,6 +10599,16 @@ Node Graph /* Use this when the bus count is determined by the node instance rather than the vtable. */ #define MA_NODE_BUS_COUNT_UNKNOWN 255 + +/* For some internal memory management of ma_node_graph. */ +typedef struct +{ + size_t offset; + size_t sizeInBytes; + unsigned char _data[1]; +} ma_stack; + + typedef struct ma_node_graph ma_node_graph; typedef void ma_node; @@ -10586,7 +10648,7 @@ typedef struct void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); /* - A callback for retrieving the number of a input frames that are required to output the + A callback for retrieving the number of input frames that are required to output the specified number of output frames. You would only want to implement this when the node performs resampling. This is optional, even for nodes that perform resampling, but it does offer a small reduction in latency as it allows miniaudio to calculate the exact number of input frames @@ -10671,10 +10733,14 @@ typedef struct ma_node_base ma_node_base; struct ma_node_base { /* These variables are set once at startup. */ - ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ + ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ const ma_node_vtable* vtable; - float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ - ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_node_input_bus* pInputBuses; + ma_node_output_bus* pOutputBuses; + float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ /* These variables are read and written only from the audio thread. */ ma_uint16 cachedFrameCountOut; @@ -10682,13 +10748,9 @@ struct ma_node_base ma_uint16 consumedFrameCountIn; /* These variables are read and written between different threads. */ - MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ - MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ - MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ - ma_uint32 inputBusCount; - ma_uint32 outputBusCount; - ma_node_input_bus* pInputBuses; - ma_node_output_bus* pOutputBuses; + MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ /* Memory management. */ ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; @@ -10724,7 +10786,8 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); typedef struct { ma_uint32 channels; - ma_uint16 nodeCacheCapInFrames; + ma_uint32 processingSizeInFrames; /* This is the preferred processing size for node processing callbacks unless overridden by a node itself. Can be 0 in which case it will be based on the frame count passed into ma_node_graph_read_pcm_frames(), but will not be well defined. */ + size_t preMixStackSizeInBytes; /* Defaults to 512KB per channel. Reducing this will save memory, but the depth of your node graph will be more restricted. */ } ma_node_graph_config; MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); @@ -10735,10 +10798,15 @@ struct ma_node_graph /* Immutable. */ ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ - ma_uint16 nodeCacheCapInFrames; + float* pProcessingCache; /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */ + ma_uint32 processingCacheFramesRemaining; + ma_uint32 processingSizeInFrames; /* Read and written by multiple threads. */ MA_ATOMIC(4, ma_bool32) isReading; + + /* Modified only by the audio thread. */ + ma_stack* pPreMixStack; }; MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); @@ -11023,6 +11091,7 @@ typedef enum MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ + MA_SOUND_FLAG_LOOPING = 0x00000020, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING */ /* ma_sound specific flags. */ MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ @@ -11062,7 +11131,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e /* Base node object for both ma_sound and ma_sound_group. */ typedef struct { - ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ + ma_node_base baseNode; /* Must be the first member for compatibility with the ma_node API. */ ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ ma_uint32 volumeSmoothTimeInPCMFrames; @@ -11122,13 +11191,13 @@ typedef struct ma_uint64 rangeEndInPCMFrames; ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointEndInPCMFrames; - ma_bool32 isLooping; ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ void* pEndCallbackUserData; #ifndef MA_NO_RESOURCE_MANAGER ma_resource_manager_pipeline_notifications initNotifications; #endif ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */ + ma_bool32 isLooping; /* Deprecated. Use the MA_SOUND_FLAG_LOOPING flag in `flags` instead. */ } ma_sound_config; MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ @@ -11192,6 +11261,7 @@ typedef struct ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */ + ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */ ma_allocation_callbacks allocationCallbacks; ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ @@ -11206,12 +11276,12 @@ MA_API ma_engine_config ma_engine_config_init(void); struct ma_engine { - ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ + ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ #if !defined(MA_NO_RESOURCE_MANAGER) ma_resource_manager* pResourceManager; #endif #if !defined(MA_NO_DEVICE_IO) - ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ #endif ma_log* pLog; ma_uint32 sampleRate; @@ -11220,10 +11290,10 @@ struct ma_engine ma_allocation_callbacks allocationCallbacks; ma_bool8 ownsResourceManager; ma_bool8 ownsDevice; - ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ - ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ - MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ - ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ + ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */ + ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; ma_mono_expansion_mode monoExpansionMode; ma_engine_process_proc onProcess; @@ -11348,6 +11418,7 @@ MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping); MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ +MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds); /* Abstraction to ma_sound_seek_to_pcm_frame() */ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); @@ -13861,7 +13932,7 @@ static ma_uint32 ma_ffs_32(ma_uint32 x) /* Just a naive implementation just to get things working for now. Will optimize this later. */ for (i = 0; i < 32; i += 1) { - if ((x & (1 << i)) != 0) { + if ((x & (1U << i)) != 0) { return i; } } @@ -14024,7 +14095,7 @@ static MA_INLINE ma_int32 ma_dither_s32(ma_dither_mode ditherMode, ma_int32 dith Atomics **************************************************************************************************************************************************************/ -/* ma_atomic.h begin */ +/* c89atomic.h begin */ #ifndef ma_atomic_h #if defined(__cplusplus) extern "C" { @@ -14750,12 +14821,12 @@ typedef int ma_atomic_memory_order; typedef ma_uint8 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_8(ptr, order) #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_8(ptr, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #else typedef ma_uint32 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_32(ptr, order) #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_32(ptr, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_32(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_32(ptr, order) #endif #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) #define MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE @@ -14836,15 +14907,24 @@ typedef int ma_atomic_memory_order; __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } + #if defined(__clang__) + #pragma clang diagnostic push + #if __clang_major__ >= 8 + #pragma clang diagnostic ignored "-Watomic-alignment" + #endif + #endif static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) { __atomic_compare_exchange_n(dst, &expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } + #if defined(__clang__) + #pragma clang diagnostic pop + #endif typedef ma_uint8 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(dst, order) (ma_bool32)__atomic_test_and_set(dst, order) #define ma_atomic_flag_clear_explicit(dst, order) __atomic_clear(dst, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #else #define ma_atomic_memory_order_relaxed 1 #define ma_atomic_memory_order_consume 2 @@ -15358,7 +15438,7 @@ typedef int ma_atomic_memory_order; typedef ma_uint8 ma_atomic_flag; #define ma_atomic_flag_test_and_set_explicit(ptr, order) (ma_bool32)ma_atomic_test_and_set_explicit_8(ptr, order) #define ma_atomic_flag_clear_explicit(ptr, order) ma_atomic_clear_explicit_8(ptr, order) - #define c89atoimc_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) + #define ma_atomic_flag_load_explicit(ptr, order) ma_atomic_load_explicit_8(ptr, order) #endif #if !defined(MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE) #if defined(MA_ATOMIC_HAS_8) @@ -15883,7 +15963,7 @@ static MA_INLINE void ma_atomic_spinlock_lock(volatile ma_atomic_spinlock* pSpin if (ma_atomic_flag_test_and_set_explicit(pSpinlock, ma_atomic_memory_order_acquire) == 0) { break; } - while (c89atoimc_flag_load_explicit(pSpinlock, ma_atomic_memory_order_relaxed) == 1) { + while (ma_atomic_flag_load_explicit(pSpinlock, ma_atomic_memory_order_relaxed) == 1) { } } } @@ -15898,7 +15978,7 @@ static MA_INLINE void ma_atomic_spinlock_unlock(volatile ma_atomic_spinlock* pSp } #endif #endif -/* ma_atomic.h end */ +/* c89atomic.h end */ #define MA_ATOMIC_SAFE_TYPE_IMPL(c89TypeExtension, type) \ static MA_INLINE ma_##type ma_atomic_##type##_get(ma_atomic_##type* x) \ @@ -16096,7 +16176,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority int result; pthread_attr_t* pAttr = NULL; -#if !defined(__EMSCRIPTEN__) +#if !defined(__EMSCRIPTEN__) && !defined(__3DS__) /* Try setting the thread priority. It's not critical if anything fails here. */ pthread_attr_t attr; if (pthread_attr_init(&attr) == 0) { @@ -16142,19 +16222,34 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority if (priority == ma_thread_priority_idle) { sched.sched_priority = priorityMin; } else if (priority == ma_thread_priority_realtime) { - sched.sched_priority = priorityMax; - } else { - sched.sched_priority += ((int)priority + 5) * priorityStep; /* +5 because the lowest priority is -5. */ - if (sched.sched_priority < priorityMin) { - sched.sched_priority = priorityMin; + #if defined(MA_PTHREAD_REALTIME_THREAD_PRIORITY) + { + sched.sched_priority = MA_PTHREAD_REALTIME_THREAD_PRIORITY; } - if (sched.sched_priority > priorityMax) { + #else + { sched.sched_priority = priorityMax; } + #endif + } else { + sched.sched_priority += ((int)priority + 5) * priorityStep; /* +5 because the lowest priority is -5. */ } - /* I'm not treating a failure of setting the priority as a critical error so not checking the return value here. */ - pthread_attr_setschedparam(&attr, &sched); + if (sched.sched_priority < priorityMin) { + sched.sched_priority = priorityMin; + } + if (sched.sched_priority > priorityMax) { + sched.sched_priority = priorityMax; + } + + /* I'm not treating a failure of setting the priority as a critical error so not aborting on failure here. */ + if (pthread_attr_setschedparam(&attr, &sched) == 0) { + #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + { + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + } + #endif + } } } } @@ -16187,7 +16282,7 @@ static void ma_thread_wait__posix(ma_thread* pThread) static ma_result ma_mutex_init__posix(ma_mutex* pMutex) { int result; - + if (pMutex == NULL) { return MA_INVALID_ARGS; } @@ -17406,7 +17501,7 @@ static ma_job_proc g_jobVTable[MA_JOB_TYPE_COUNT] = /* Device. */ #if !defined(MA_NO_DEVICE_IO) - ma_job_process__device__aaudio_reroute /*MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE*/ + ma_job_process__device__aaudio_reroute /* MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE */ #endif }; @@ -17751,7 +17846,7 @@ MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob) is stored. One thread can fall through to the freeing of this item while another is still using "head" for the retrieval of the "next" variable. - The slot allocator might need to make use of some reference counting to ensure it's only truely freed when + The slot allocator might need to make use of some reference counting to ensure it's only truly freed when there are no more references to the item. This must be fixed before removing these locks. */ @@ -17859,7 +17954,16 @@ MA_API void ma_dlclose(ma_log* pLog, ma_handle handle) #ifdef MA_WIN32 FreeLibrary((HMODULE)handle); #else - dlclose((void*)handle); + /* Hack for Android bug (see https://github.com/android/ndk/issues/360). Calling dlclose() pre-API 28 may segfault. */ + #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + { + dlclose((void*)handle); + } + #else + { + (void)handle; + } + #endif #endif (void)pLog; @@ -17880,12 +17984,12 @@ MA_API ma_proc ma_dlsym(ma_log* pLog, ma_handle handle, const char* symbol) #ifdef _WIN32 proc = (ma_proc)GetProcAddress((HMODULE)handle, symbol); #else -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #endif proc = (ma_proc)dlsym((void*)handle, symbol); -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif @@ -17923,9 +18027,13 @@ DEVICE I/O #endif #endif +#ifdef MA_APPLE + #include +#endif + #ifndef MA_NO_DEVICE_IO -#if defined(MA_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +#if defined(MA_APPLE) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101200) #include /* For mach_absolute_time() */ #endif @@ -17939,6 +18047,10 @@ DEVICE I/O #endif #endif +/* This must be set to at least 26. */ +#ifndef MA_AAUDIO_MIN_ANDROID_SDK_VERSION +#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 27 +#endif MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags) @@ -18085,7 +18197,7 @@ MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend) #if defined(MA_HAS_AAUDIO) #if defined(MA_ANDROID) { - return ma_android_sdk_version() >= 26; + return ma_android_sdk_version() >= MA_AAUDIO_MIN_ANDROID_SDK_VERSION; } #else return MA_FALSE; @@ -18402,7 +18514,6 @@ typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, const char* lpValueName, DWORD* lpReserved, DWORD* lpType, BYTE* lpData, DWORD* lpcbData); #endif /* MA_WIN32_DESKTOP */ - MA_API size_t ma_strlen_WCHAR(const WCHAR* str) { size_t len = 0; @@ -18487,7 +18598,7 @@ Timing return (double)(counter.QuadPart - pTimer->counter) / g_ma_TimerFrequency.QuadPart; } -#elif defined(MA_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +#elif defined(MA_APPLE) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101200) static ma_uint64 g_ma_TimerFrequency = 0; static void ma_timer_init(ma_timer* pTimer) { @@ -18670,11 +18781,16 @@ static void ma_device__on_notification_rerouted(ma_device* pDevice) #endif #if defined(MA_EMSCRIPTEN) -EMSCRIPTEN_KEEPALIVE -void ma_device__on_notification_unlocked(ma_device* pDevice) +#ifdef __cplusplus +extern "C" { +#endif +void EMSCRIPTEN_KEEPALIVE ma_device__on_notification_unlocked(ma_device* pDevice) { ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_unlocked)); } +#ifdef __cplusplus +} +#endif #endif @@ -18802,7 +18918,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut unsigned int prevDenormalState = ma_device_disable_denormals(pDevice); { /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ - if (pFramesIn != NULL && masterVolumeFactor < 1) { + if (pFramesIn != NULL && masterVolumeFactor != 1) { ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); @@ -18825,7 +18941,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut /* Volume control and clipping for playback devices. */ if (pFramesOut != NULL) { - if (masterVolumeFactor < 1) { + if (masterVolumeFactor != 1) { if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); } @@ -18837,6 +18953,11 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut } } ma_device_restore_denormals(pDevice, prevDenormalState); + } else { + /* No data callback. Just silence the output. */ + if (pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); + } } } @@ -18922,9 +19043,7 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra framesToReadThisIterationIn = requiredInputFrameCount; } - if (framesToReadThisIterationIn > 0) { - ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); - } + ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); /* At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any @@ -18965,7 +19084,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame ma_uint64 totalClientFramesProcessed = 0; const void* pRunningFramesInDeviceFormat = pFramesInDeviceFormat; - /* We just keep going until we've exhaused all of our input frames and cannot generate any more output frames. */ + /* We just keep going until we've exhausted all of our input frames and cannot generate any more output frames. */ for (;;) { ma_uint64 deviceFramesProcessedThisIteration; ma_uint64 clientFramesProcessedThisIteration; @@ -19248,7 +19367,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) } /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small + If we weren't able to generate any output frames it must mean we've exhausted all of our input. The only time this would not be the case is if capturedClientData was too small which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. */ if (capturedClientFramesToProcessThisIteration == 0) { @@ -19451,7 +19570,7 @@ static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 oper /* The first thing to do is wait for an operation slot to become available. We only have a single slot for this, but we could extend this later - to support queing of operations. + to support queuing of operations. */ result = ma_semaphore_wait(&pDevice->null_device.operationSemaphore); if (result != MA_SUCCESS) { @@ -21268,7 +21387,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context } /* - Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently supported on + Exclusive Mode. We repeatedly call IsFormatSupported() here. This is not currently supported on UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here on out, MA_SUCCESS is guaranteed to be returned. */ @@ -21473,8 +21592,23 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device MA_ASSERT(pContext != NULL); MA_ASSERT(ppMMDevice != NULL); - hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); - if (FAILED(hr)) { + /* + This weird COM init/uninit here is a hack to work around a crash when changing devices. What is happening is + WASAPI fires a callback from another thread when the device is changed. It's from that thread where this + function is getting called. What I'm suspecting is that the other thread is not initializing COM which in turn + results in CoCreateInstance() failing. + + The community has reported that this seems to fix the crash. There are future plans to move all WASAPI operation + over to a single thread to make everything safer, but in the meantime while we wait for that to come online I'm + happy enough to use this hack instead. + */ + ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); + { + hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + } + ma_CoUninitialize(pContext); + + if (FAILED(hr)) { /* <-- This is checking the call above to ma_CoCreateInstance(). */ ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.\n"); return ma_result_from_HRESULT(hr); } @@ -21506,7 +21640,7 @@ static ma_result ma_context_get_device_id_from_MMDevice__wasapi(ma_context* pCon size_t idlen = ma_strlen_WCHAR(pDeviceIDString); if (idlen+1 > ma_countof(pDeviceID->wasapi)) { ma_CoTaskMemFree(pContext, pDeviceIDString); - MA_ASSERT(MA_FALSE); /* NOTE: If this is triggered, please report it. It means the format of the ID must haved change and is too long to fit in our fixed sized buffer. */ + MA_ASSERT(MA_FALSE); /* NOTE: If this is triggered, please report it. It means the format of the ID must have changed and is too long to fit in our fixed sized buffer. */ return MA_ERROR; } @@ -21950,12 +22084,16 @@ static ma_result ma_device_uninit__wasapi(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); -#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - if (pDevice->wasapi.pDeviceEnumerator) { - ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); - ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) + { + if (pDevice->wasapi.pDeviceEnumerator) { + ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); + ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); + } + + ma_mutex_uninit(&pDevice->wasapi.rerouteLock); } -#endif + #endif if (pDevice->wasapi.pRenderClient) { if (pDevice->wasapi.pMappedBufferPlayback != NULL) { @@ -22256,7 +22394,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * pData->periodsOut * 10; /* - If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing + If the periodicity is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing it and trying it again. */ hr = E_FAIL; @@ -22266,7 +22404,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device if (bufferDuration > 500*10000) { break; } else { - if (bufferDuration == 0) { /* <-- Just a sanity check to prevent an infinit loop. Should never happen, but it makes me feel better. */ + if (bufferDuration == 0) { /* <-- Just a sanity check to prevent an infinite loop. Should never happen, but it makes me feel better. */ break; } @@ -23005,6 +23143,14 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + /* If we have a mapped buffer we need to release it. */ + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; + } + hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); @@ -23018,31 +23164,34 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) return ma_result_from_HRESULT(hr); } - /* If we have a mapped buffer we need to release it. */ - if (pDevice->wasapi.pMappedBufferCapture != NULL) { - ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - pDevice->wasapi.mappedBufferCaptureLen = 0; - } - ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_FALSE); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + ma_silence_pcm_frames( + ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), + pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen, + pDevice->playback.internalFormat, pDevice->playback.internalChannels + ); + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + } + /* The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played. */ if (ma_atomic_bool32_get(&pDevice->wasapi.isStartedPlayback)) { /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ - DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / pDevice->playback.internalSampleRate; + DWORD waitTime = (pDevice->wasapi.actualBufferSizeInFramesPlayback * 1000) / pDevice->playback.internalSampleRate; if (pDevice->playback.shareMode == ma_share_mode_exclusive) { WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); - } - else { - ma_uint32 prevFramesAvaialablePlayback = (ma_uint32)-1; + } else { + ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1; ma_uint32 framesAvailablePlayback; for (;;) { result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); @@ -23058,13 +23207,13 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case. */ - if (framesAvailablePlayback == prevFramesAvaialablePlayback) { + if (framesAvailablePlayback == prevFramesAvailablePlayback) { break; } - prevFramesAvaialablePlayback = framesAvailablePlayback; + prevFramesAvailablePlayback = framesAvailablePlayback; - WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime * 1000); ResetEvent((HANDLE)pDevice->wasapi.hEventPlayback); /* Manual reset. */ + WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); } } } @@ -23076,19 +23225,20 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) } /* The audio client needs to be reset otherwise restarting will fail. */ - hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + { + ma_int32 retries = 5; + + while ((hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback)) == MA_AUDCLNT_E_BUFFER_OPERATION_PENDING && retries > 0) { + ma_sleep(10); + retries -= 1; + } + } + if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device."); return ma_result_from_HRESULT(hr); } - if (pDevice->wasapi.pMappedBufferPlayback != NULL) { - ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); - pDevice->wasapi.pMappedBufferPlayback = NULL; - pDevice->wasapi.mappedBufferPlaybackCap = 0; - pDevice->wasapi.mappedBufferPlaybackLen = 0; - } - ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_FALSE); } @@ -23655,6 +23805,13 @@ DirectSound Backend #define MA_DSBPLAY_TERMINATEBY_DISTANCE 0x00000010 #define MA_DSBPLAY_TERMINATEBY_PRIORITY 0x00000020 +#define MA_DSBSTATUS_PLAYING 0x00000001 +#define MA_DSBSTATUS_BUFFERLOST 0x00000002 +#define MA_DSBSTATUS_LOOPING 0x00000004 +#define MA_DSBSTATUS_LOCHARDWARE 0x00000008 +#define MA_DSBSTATUS_LOCSOFTWARE 0x00000010 +#define MA_DSBSTATUS_TERMINATED 0x00000020 + #define MA_DSCBSTART_LOOPING 0x00000001 typedef struct @@ -24024,9 +24181,12 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma } /* The cooperative level must be set before doing anything else. */ - hWnd = ((MA_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + hWnd = (HWND)pContext->dsound.hWnd; if (hWnd == 0) { - hWnd = ((MA_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + hWnd = ((MA_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + if (hWnd == 0) { + hWnd = ((MA_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + } } hr = ma_IDirectSound_SetCooperativeLevel(pDirectSound, hWnd, (shareMode == ma_share_mode_exclusive) ? MA_DSSCL_EXCLUSIVE : MA_DSSCL_PRIORITY); @@ -24530,8 +24690,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf } /* - Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices. We need to initialize - the capture device first because we'll want to match it's buffer size and period count on the playback side if we're using + Unfortunately DirectSound uses different APIs and data structures for playback and capture devices. We need to initialize + the capture device first because we'll want to match its buffer size and period count on the playback side if we're using full-duplex mode. */ if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { @@ -24814,6 +24974,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) ma_bool32 isPlaybackDeviceStarted = MA_FALSE; ma_uint32 framesWrittenToPlaybackDevice = 0; /* For knowing whether or not the playback device needs to be started. */ ma_uint32 waitTimeInMilliseconds = 1; + DWORD playbackBufferStatus = 0; MA_ASSERT(pDevice != NULL); @@ -25142,6 +25303,20 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) break; } + hr = ma_IDirectSoundBuffer_GetStatus((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &playbackBufferStatus); + if (SUCCEEDED(hr) && (playbackBufferStatus & MA_DSBSTATUS_PLAYING) == 0 && isPlaybackDeviceStarted) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[DirectSound] Attempting to resume audio due to state: %d.", (int)playbackBufferStatus); + hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); + if (FAILED(hr)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed after attempting to resume from state %d.", (int)playbackBufferStatus); + return ma_result_from_HRESULT(hr); + } + + isPlaybackDeviceStarted = MA_TRUE; + ma_sleep(waitTimeInMilliseconds); + continue; + } + if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; } @@ -25343,6 +25518,8 @@ static ma_result ma_context_init__dsound(ma_context* pContext, const ma_context_ return MA_API_NOT_FOUND; } + pContext->dsound.hWnd = pConfig->dsound.hWnd; + pCallbacks->onContextInit = ma_context_init__dsound; pCallbacks->onContextUninit = ma_context_uninit__dsound; pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__dsound; @@ -25665,7 +25842,7 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, - If the name GUID is not present in the registry we'll also need to stick to the original 31 characters. - I like consistency, so I want the returned device names to be consistent with those returned by WASAPI and DirectSound. The problem, however is that WASAPI and DirectSound use " ()" format (such as "Speakers (High Definition Audio)"), - but WinMM does not specificy the component name. From my admittedly limited testing, I've notice the component name seems to + but WinMM does not specify the component name. From my admittedly limited testing, I've notice the component name seems to usually fit within the 31 characters of the fixed sized buffer, so what I'm going to do is parse that string for the component name, and then concatenate the name from the registry. */ @@ -25933,7 +26110,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi return MA_DEVICE_TYPE_NOT_SUPPORTED; } - /* No exlusive mode with WinMM. */ + /* No exclusive mode with WinMM. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; @@ -25955,7 +26132,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi /* We use an event to know when a new fragment needs to be enqueued. */ pDevice->winmm.hEventCapture = (ma_handle)CreateEventA(NULL, TRUE, TRUE, NULL); if (pDevice->winmm.hEventCapture == NULL) { - errorMsg = "[WinMM] Failed to create event for fragment enqueing for the capture device.", errorCode = ma_result_from_GetLastError(GetLastError()); + errorMsg = "[WinMM] Failed to create event for fragment enqueuing for the capture device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; } @@ -25993,7 +26170,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi /* We use an event to know when a new fragment needs to be enqueued. */ pDevice->winmm.hEventPlayback = (ma_handle)CreateEventA(NULL, TRUE, TRUE, NULL); if (pDevice->winmm.hEventPlayback == NULL) { - errorMsg = "[WinMM] Failed to create event for fragment enqueing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); + errorMsg = "[WinMM] Failed to create event for fragment enqueuing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; } @@ -27115,7 +27292,7 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s /* We're trying to open a specific device. There's a few things to consider here: - miniaudio recongnizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When + miniaudio recognizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When an ID of this format is specified, it indicates to miniaudio that it can try different combinations of plugins ("hw", "dmix", etc.) until it finds an appropriate one that works. This comes in very handy when trying to open a device in shared mode ("dmix"), vs exclusive mode ("hw"). */ @@ -27214,7 +27391,7 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu /* At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device - initialization time and is used as an indicator to try and use the most appropriate plugin depending on the + initialization time and is used as an indicator to try to use the most appropriate plugin depending on the device type and sharing mode. */ char* dst = hwid; @@ -27393,7 +27570,7 @@ static void ma_context_iterate_rates_and_add_native_data_format__alsa(ma_context ((ma_snd_pcm_hw_params_get_rate_min_proc)pContext->alsa.snd_pcm_hw_params_get_rate_min)(pHWParams, &minSampleRate, &sampleRateDir); ((ma_snd_pcm_hw_params_get_rate_max_proc)pContext->alsa.snd_pcm_hw_params_get_rate_max)(pHWParams, &maxSampleRate, &sampleRateDir); - /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculus. */ + /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculous. */ minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); @@ -27469,10 +27646,10 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic /* Some ALSA devices can support many permutations of formats, channels and rates. We only support a fixed number of permutations which means we need to employ some strategies to ensure the best - combinations are returned. An example is the "pulse" device which can do it's own data conversion + combinations are returned. An example is the "pulse" device which can do its own data conversion in software and as a result can support any combination of format, channels and rate. - We want to ensure the the first data formats are the best. We have a list of favored sample + We want to ensure that the first data formats are the best. We have a list of favored sample formats and sample rates, so these will be the basis of our iteration. */ @@ -28050,7 +28227,21 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* Don't need to do anything for playback because it'll be started automatically when enough data has been written. */ + /* + When data is written to the device we wait for the device to get ready to receive data with poll(). In my testing + I have observed that poll() can sometimes block forever unless the device is started explicitly with snd_pcm_start() + or some data is written with snd_pcm_writei(). + + To resolve this I've decided to do an explicit start with snd_pcm_start(). The problem with this is that the device + is started without any data in the internal buffer which will result in an immediate underrun. If instead we were + to call into snd_pcm_writei() in an attempt to prevent the underrun, we would run the risk of a weird deadlock + issue as documented inside ma_device_write__alsa(). + */ + resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); + if (resultALSA < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device."); + return ma_result_from_errno(-resultALSA); + } } return MA_SUCCESS; @@ -28063,6 +28254,7 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) a small chance that our wakeupfd has not been cleared. We'll clear that out now if applicable. */ int resultPoll; + int resultRead; if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...\n"); @@ -28077,12 +28269,15 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful.\n"); } - /* Clear the wakeupfd. */ - resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - read(((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture)[0].fd, &t, sizeof(t)); - } + /* Clear the wakeupfd. */ + resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + resultRead = read(((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture)[0].fd, &t, sizeof(t)); + if (resultRead != sizeof(t)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from capture wakeupfd. read() = %d\n", resultRead); + } + } } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { @@ -28099,12 +28294,14 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) } /* Clear the wakeupfd. */ - resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - read(((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback)[0].fd, &t, sizeof(t)); - } - + resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + resultRead = read(((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback)[0].fd, &t, sizeof(t)); + if (resultRead != sizeof(t)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from playback wakeupfd. read() = %d\n", resultRead); + } + } } return MA_SUCCESS; @@ -28117,13 +28314,20 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st int resultALSA; int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1); if (resultPoll < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed.\n"); - return ma_result_from_errno(errno); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] poll() failed.\n"); + + /* + There have been reports that poll() is returning an error randomly and that instead of + returning an error, simply trying again will work. I'm experimenting with adopting this + advice. + */ + continue; + /*return ma_result_from_errno(errno);*/ } /* Before checking the ALSA poll descriptor flag we need to check if the wakeup descriptor - has had it's POLLIN flag set. If so, we need to actually read the data and then exit + has had it's POLLIN flag set. If so, we need to actually read the data and then exit the function. The wakeup descriptor will be the first item in the descriptors buffer. */ if ((pPollDescriptors[0].revents & POLLIN) != 0) { @@ -28152,7 +28356,7 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st ma_snd_pcm_state_t state = ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM); if (state == MA_SND_PCM_STATE_XRUN) { /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ - } else { + } else { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d\n", ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM)); } } @@ -28585,7 +28789,7 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co return MA_SUCCESS; } -#endif /* ALSA */ +#endif /* MA_HAS_ALSA */ @@ -28596,7 +28800,7 @@ PulseAudio Backend ******************************************************************************/ #ifdef MA_HAS_PULSEAUDIO /* -The PulseAudio API, along with Apple's Core Audio, is the worst of the maintream audio APIs. This is a brief description of what's going on +The PulseAudio API, along with Apple's Core Audio, is the worst of the mainstream audio APIs. This is a brief description of what's going on in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion. PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it @@ -28611,7 +28815,7 @@ get fun, and I don't mean that in a good way... The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is -enabled through the use of a "main loop". In the asychronous API you cannot get away from the main loop, and the main loop is where almost +enabled through the use of a "main loop". In the asynchronous API you cannot get away from the main loop, and the main loop is where almost all of PulseAudio's problems stem from. When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own @@ -28661,7 +28865,7 @@ because PulseAudio takes it literally, specifically the "can be". You would thin writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified, -PulseAudio will immediately fire it's write or read callback. This is *technically* correct (based on the wording in the documentation) +PulseAudio will immediately fire its write or read callback. This is *technically* correct (based on the wording in the documentation) because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback would be where a program will want to write or read data to or from the stream, but when it's called before the application has even requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at @@ -30039,16 +30243,18 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { - static int g_StreamCounter = 0; + static ma_atomic_uint32 g_StreamCounter = { 0 }; char actualStreamName[256]; if (pStreamName != NULL) { ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); } else { - ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); - ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + const char* pBaseName = "miniaudio:"; + size_t baseNameLen = strlen(pBaseName); + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), pBaseName); + ma_itoa_s((int)ma_atomic_uint32_get(&g_StreamCounter), actualStreamName + baseNameLen, sizeof(actualStreamName)-baseNameLen, 10); } - g_StreamCounter += 1; + ma_atomic_uint32_fetch_add(&g_StreamCounter, 1); return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } @@ -30302,6 +30508,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ma_pa_buffer_attr attr; const ma_pa_sample_spec* pActualSS = NULL; const ma_pa_buffer_attr* pActualAttr = NULL; + const ma_pa_channel_map* pActualChannelMap = NULL; ma_uint32 iChannel; ma_pa_stream_flags_t streamFlags; @@ -30362,7 +30569,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MA_PA_CHANNEL_MAP_DEFAULT); + ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, pConfig->pulse.channelMap); /* Use the requested sample rate if one was specified. */ if (pDescriptorCapture->sampleRate != 0) { @@ -30451,7 +30658,12 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi goto on_error4; } + /* Internal channel map. */ + pActualChannelMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (pActualChannelMap == NULL) { + pActualChannelMap = &cmap; /* Fallback just in case. */ + } /* Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting @@ -30461,8 +30673,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi fixed sooner than later. I might remove this hack later. */ if (pDescriptorCapture->channels > 2) { - for (iChannel = 0; iChannel < pDescriptorCapture->channels; ++iChannel) { - pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + for (iChannel = 0; iChannel < pDescriptorCapture->channels; iChannel += 1) { + pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(pActualChannelMap->map[iChannel]); } } else { /* Hack for mono and stereo. */ @@ -30509,7 +30721,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MA_PA_CHANNEL_MAP_DEFAULT); + ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, pConfig->pulse.channelMap); /* Use the requested sample rate if one was specified. */ @@ -30603,7 +30815,12 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi goto on_error4; } + /* Internal channel map. */ + pActualChannelMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (pActualChannelMap == NULL) { + pActualChannelMap = &cmap; /* Fallback just in case. */ + } /* Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting @@ -30613,8 +30830,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi fixed sooner than later. I might remove this hack later. */ if (pDescriptorPlayback->channels > 2) { - for (iChannel = 0; iChannel < pDescriptorPlayback->channels; ++iChannel) { - pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + for (iChannel = 0; iChannel < pDescriptorPlayback->channels; iChannel += 1) { + pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(pActualChannelMap->map[iChannel]); } } else { /* Hack for mono and stereo. */ @@ -31767,7 +31984,7 @@ static ma_result ma_context_init__jack(ma_context* pContext, const ma_context_co return MA_SUCCESS; } -#endif /* JACK */ +#endif /* MA_HAS_JACK */ @@ -31858,7 +32075,7 @@ that supports this level of detail. There was some public domain sample code I s and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the distinction between playback and capture in particular). Therefore, miniaudio is using the AudioObject API. -Most (all?) functions in the AudioObject API take a AudioObjectID as it's input. This is the device identifier. When +Most (all?) functions in the AudioObject API take a AudioObjectID as its input. This is the device identifier. When retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be @@ -32193,6 +32410,12 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* #define AUDIO_OBJECT_PROPERTY_ELEMENT kAudioObjectPropertyElementMaster #endif +/* kAudioDevicePropertyScope* were renamed to kAudioObjectPropertyScope* in 10.8. */ +#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8) +#define kAudioObjectPropertyScopeInput kAudioDevicePropertyScopeInput +#define kAudioObjectPropertyScopeOutput kAudioDevicePropertyScopeOutput +#endif + static ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) /* NOTE: Free the returned buffer with ma_free(). */ { AudioObjectPropertyAddress propAddressDevices; @@ -32782,7 +33005,7 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec desiredSampleRate = sampleRate; if (desiredSampleRate == 0) { - desiredSampleRate = pOrigFormat->mSampleRate; + desiredSampleRate = (ma_uint32)pOrigFormat->mSampleRate; } desiredChannelCount = channels; @@ -33425,7 +33648,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl } } else { /* This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This assumes each buffer is the same size. */ - MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should heve been validated at initialization time. */ + MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ /* For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something @@ -33516,11 +33739,12 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla */ for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { pRenderedBufferList->mBuffers[iBuffer].mDataByteSize = pDevice->coreaudio.audioBufferCapInFrames * ma_get_bytes_per_sample(pDevice->capture.internalFormat) * pRenderedBufferList->mBuffers[iBuffer].mNumberChannels; + /*printf("DEBUG: nDataByteSize = %d\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d.\n", (int)status); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "ERROR: AudioUnitRender() failed with %d.\n", (int)status); return status; } @@ -33756,7 +33980,7 @@ static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContex ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); { - /* Don't do anything if we've already initializd device tracking. */ + /* Don't do anything if we've already initialized device tracking. */ if (g_DeviceTrackingInitCounter_CoreAudio == 0) { AudioObjectPropertyAddress propAddress; propAddress.mScope = kAudioObjectPropertyScopeGlobal; @@ -34068,11 +34292,11 @@ typedef struct static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */ { - ma_result result; + ma_result result = MA_SUCCESS; OSStatus status; UInt32 enableIOFlag; AudioStreamBasicDescription bestFormat; - UInt32 actualPeriodSizeInFrames; + ma_uint32 actualPeriodSizeInFrames; AURenderCallbackStruct callbackInfo; #if defined(MA_APPLE_DESKTOP) AudioObjectID deviceObjectID; @@ -34224,7 +34448,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev returning a result code of -10863. I have also tried changing the format directly on the input scope on the input bus, but this just results in `ca_require: IsStreamFormatWritable(inScope, inElement) NotWritable` when trying to set the format. - Something that does seem to work, however, has been setting the nominal sample rate on the deivce object. The problem with + Something that does seem to work, however, has been setting the nominal sample rate on the device object. The problem with this, however, is that it actually changes the sample rate at the operating system level and not just the application. This could be intrusive to the user, however, so I don't think it's wise to make this the default. Instead I'm making this a configuration option. When the `coreaudio.allowNominalSampleRateChange` config option is set to true, changing the sample @@ -34275,15 +34499,28 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev /* I've had a report that the channel count returned by AudioUnitGetProperty above is inconsistent with AVAudioSession outputNumberOfChannels. I'm going to try using the AVAudioSession values instead. + + UPDATE 20/02/2025: + When testing on the simulator with an iPhone 15 and iOS 17 I get an error when initializing the audio + unit if set the input channels to pAudioSession.inputNumberOfChannels. What is happening is the channel + count returned from AudioUnitGetProperty() above is set to 2, but pAudioSession is reporting a channel + count of 1. When this happens, the call to AudioUnitSetProprty() below just down below will succeed, but + AudioUnitInitialize() further down will fail. The only solution I have come up with is to not set the + channel count to pAudioSession.inputNumberOfChannels. */ if (deviceType == ma_device_type_playback) { bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.outputNumberOfChannels; } + + #if 0 if (deviceType == ma_device_type_capture) { + /*printf("DEBUG: bestFormat.mChannelsPerFrame = %d; pAudioSession.inputNumberOfChannels = %d\n", (int)bestFormat.mChannelsPerFrame, (int)pAudioSession.inputNumberOfChannels);*/ bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.inputNumberOfChannels; } + #endif } + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); @@ -34303,7 +34540,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } pData->channelsOut = bestFormat.mChannelsPerFrame; - pData->sampleRateOut = bestFormat.mSampleRate; + pData->sampleRateOut = (ma_uint32)bestFormat.mSampleRate; } /* Clamp the channel count for safety. */ @@ -34610,7 +34847,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); /* - If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly + If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly switch the device in the background. */ if (pConfig->capture.pDeviceID == NULL) { @@ -34674,7 +34911,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); /* - If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly + If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly switch the device in the background. */ if (pDescriptorPlayback->pDeviceID == NULL && (pConfig->deviceType != ma_device_type_duplex || pDescriptorCapture->pDeviceID != NULL)) { @@ -34992,7 +35229,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte return MA_SUCCESS; } -#endif /* Core Audio */ +#endif /* MA_HAS_COREAUDIO */ @@ -35484,7 +35721,7 @@ static ma_result ma_device_uninit__sndio(ma_device* pDevice) ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); } - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); } @@ -35839,7 +36076,7 @@ static ma_result ma_context_init__sndio(ma_context* pContext, const ma_context_c (void)pConfig; return MA_SUCCESS; } -#endif /* sndio */ +#endif /* MA_HAS_SNDIO */ @@ -35857,6 +36094,10 @@ audio(4) Backend #include #include +#ifdef __NetBSD__ +#include +#endif + #if defined(__OpenBSD__) #include #if defined(OpenBSD) && OpenBSD >= 201709 @@ -36076,9 +36317,15 @@ static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext ma_uint32 channels; ma_uint32 sampleRate; +#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) + if (ioctl(fd, AUDIO_GETFORMAT, &fdInfo) < 0) { + return MA_ERROR; + } +#else if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { return MA_ERROR; } +#endif if (deviceType == ma_device_type_playback) { channels = fdInfo.play.channels; @@ -36356,7 +36603,11 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We're using a default device. Get the info from the /dev/audioctl file instead of /dev/audio. */ int fdctl = open(pDefaultDeviceCtlNames[iDefaultDevice], fdFlags, 0); if (fdctl != -1) { +#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) + fdInfoResult = ioctl(fdctl, AUDIO_GETFORMAT, &fdInfo); +#else fdInfoResult = ioctl(fdctl, AUDIO_GETINFO, &fdInfo); +#endif close(fdctl); } } @@ -36723,7 +36974,7 @@ static ma_result ma_context_init__audio4(ma_context* pContext, const ma_context_ return MA_SUCCESS; } -#endif /* audio4 */ +#endif /* MA_HAS_AUDIO4 */ /****************************************************************************** @@ -37086,7 +37337,7 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf } /* - The OSS documantation is very clear about the order we should be initializing the device's properties: + The OSS documentation is very clear about the order we should be initializing the device's properties: 1) Format 2) Channels 3) Sample rate. @@ -37354,7 +37605,7 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con return MA_SUCCESS; } -#endif /* OSS */ +#endif /* MA_HAS_OSS */ @@ -37367,7 +37618,9 @@ AAudio Backend ******************************************************************************/ #ifdef MA_HAS_AAUDIO -/*#include */ +#ifdef MA_NO_RUNTIME_LINKING + #include +#endif typedef int32_t ma_aaudio_result_t; typedef int32_t ma_aaudio_direction_t; @@ -37580,9 +37833,7 @@ static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUs MA_ASSERT(pDevice != NULL); (void)error; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); - /* When we get an error, we'll assume that the stream is in an erroneous state and needs to be restarted. From the documentation, we cannot do this from the error callback. Therefore we are going to use an event thread for the AAudio backend to do this @@ -37610,7 +37861,9 @@ static ma_aaudio_data_callback_result_t ma_stream_data_callback_capture__aaudio( ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - ma_device_handle_backend_data_callback(pDevice, NULL, pAudioData, frameCount); + if (frameCount > 0) { + ma_device_handle_backend_data_callback(pDevice, NULL, pAudioData, (ma_uint32)frameCount); + } (void)pStream; return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; @@ -37621,7 +37874,14 @@ static ma_aaudio_data_callback_result_t ma_stream_data_callback_playback__aaudio ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - ma_device_handle_backend_data_callback(pDevice, pAudioData, NULL, frameCount); + /* + I've had a report that AAudio can sometimes post a frame count of 0. We need to check for that here + so we don't get any errors at a deeper level. I'm doing the same with the capture side for safety, + though I've not yet had any reports about that one. + */ + if (frameCount > 0) { + ma_device_handle_backend_data_callback(pDevice, pAudioData, NULL, (ma_uint32)frameCount); + } (void)pStream; return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; @@ -37656,32 +37916,25 @@ static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* ((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pDescriptor->sampleRate); } - if (deviceType == ma_device_type_capture) { - if (pDescriptor->channels != 0) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); - } - if (pDescriptor->format != ma_format_unknown) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); - } - } else { - if (pDescriptor->channels != 0) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); - } - if (pDescriptor->format != ma_format_unknown) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); - } + if (pDescriptor->channels != 0) { + ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); + } + + if (pDescriptor->format != ma_format_unknown) { + ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); } /* - There have been reports where setting the frames per data callback results in an error - later on from Android. To address this, I'm experimenting with simply not setting it on - anything from Android 11 and earlier. Suggestions welcome on how we might be able to make - this more targetted. + There have been reports where setting the frames per data callback results in an error. + In particular, re-routing may inadvertently switch from low-latency mode, resulting in a less stable + stream from the legacy path (AudioStreamLegacy). To address this, we simply don't set the value. It + can still be set if it's explicitly requested via the aaudio.allowSetBufferCapacity variable in the + device config. */ - if (!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30) { + if ((!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30) && pConfig->aaudio.allowSetBufferCapacity) { /* - AAudio is annoying when it comes to it's buffer calculation stuff because it doesn't let you + AAudio is annoying when it comes to its buffer calculation stuff because it doesn't let you retrieve the actual sample rate until after you've opened the stream. But you need to configure the buffer capacity before you open the stream... :/ @@ -37715,7 +37968,11 @@ static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_playback__aaudio, (void*)pDevice); } - /* Not sure how this affects things, but since there's a mapping between miniaudio's performance profiles and AAudio's performance modes, let go ahead and set it. */ + /* + If we set AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, we allow for MMAP (non-legacy path). + Since there's a mapping between miniaudio's performance profiles and AAudio's performance modes, let's use it. + Beware though, with a conservative performance profile, AAudio will indeed take the legacy path. + */ ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, (pConfig->performanceProfile == ma_performance_profile_low_latency) ? MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY : MA_AAUDIO_PERFORMANCE_MODE_NONE); /* We need to set an error callback to detect device changes. */ @@ -37751,6 +38008,9 @@ static ma_result ma_open_stream_basic__aaudio(ma_context* pContext, const ma_dev return result; } + /* Let's give AAudio a hint to avoid the legacy path (AudioStreamLegacy). */ + ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + return ma_open_stream_and_close_builder__aaudio(pContext, pBuilder, ppStream); } @@ -37775,6 +38035,10 @@ static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_conf static ma_result ma_close_stream__aaudio(ma_context* pContext, ma_AAudioStream* pStream) { + if (pStream == NULL) { + return MA_INVALID_ARGS; + } + return ma_result_from_aaudio(((MA_PFN_AAudioStream_close)pContext->aaudio.AAudioStream_close)(pStream)); } @@ -37901,20 +38165,36 @@ static ma_result ma_context_get_device_info__aaudio(ma_context* pContext, ma_dev return MA_SUCCESS; } +static ma_result ma_close_streams__aaudio(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + /* When re-routing, streams may have been closed and never re-opened. Hence the extra checks below. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + pDevice->aaudio.pStreamCapture = NULL; + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + pDevice->aaudio.pStreamPlayback = NULL; + } + + return MA_SUCCESS; +} static ma_result ma_device_uninit__aaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - pDevice->aaudio.pStreamCapture = NULL; + /* Wait for any rerouting to finish before attempting to close the streams. */ + ma_mutex_lock(&pDevice->aaudio.rerouteLock); + { + ma_close_streams__aaudio(pDevice); } + ma_mutex_unlock(&pDevice->aaudio.rerouteLock); - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - pDevice->aaudio.pStreamPlayback = NULL; - } + /* Destroy re-routing lock. */ + ma_mutex_uninit(&pDevice->aaudio.rerouteLock); return MA_SUCCESS; } @@ -37966,7 +38246,7 @@ static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_dev return MA_SUCCESS; } -static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_device_init_streams__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; @@ -37999,6 +38279,25 @@ static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_conf return MA_SUCCESS; } +static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +{ + ma_result result; + + MA_ASSERT(pDevice != NULL); + + result = ma_device_init_streams__aaudio(pDevice, pConfig, pDescriptorPlayback, pDescriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_mutex_init(&pDevice->aaudio.rerouteLock); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + static ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) { ma_aaudio_result_t resultAA; @@ -38006,12 +38305,16 @@ static ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStr MA_ASSERT(pDevice != NULL); + if (pStream == NULL) { + return MA_INVALID_ARGS; + } + resultAA = ((MA_PFN_AAudioStream_requestStart)pDevice->pContext->aaudio.AAudioStream_requestStart)(pStream); if (resultAA != MA_AAUDIO_OK) { return ma_result_from_aaudio(resultAA); } - /* Do we actually need to wait for the device to transition into it's started state? */ + /* Do we actually need to wait for the device to transition into its started state? */ /* The device should be in either a starting or started state. If it's not set to started we need to wait for it to transition. It should go from starting to started. */ currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); @@ -38038,6 +38341,10 @@ static ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStre MA_ASSERT(pDevice != NULL); + if (pStream == NULL) { + return MA_INVALID_ARGS; + } + /* From the AAudio documentation: @@ -38123,22 +38430,20 @@ static ma_result ma_device_stop__aaudio(ma_device* pDevice) static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) { ma_result result; + int32_t retries = 0; MA_ASSERT(pDevice != NULL); - /* The first thing to do is close the streams. */ - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - pDevice->aaudio.pStreamCapture = NULL; - } - - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - pDevice->aaudio.pStreamPlayback = NULL; - } - - /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ + /* + TODO: Stop retrying if main thread is about to uninit device. + */ + ma_mutex_lock(&pDevice->aaudio.rerouteLock); { +error_disconnected: + /* The first thing to do is close the streams. */ + ma_close_streams__aaudio(pDevice); + + /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ ma_device_config deviceConfig; ma_device_descriptor descriptorPlayback; ma_device_descriptor descriptorCapture; @@ -38187,15 +38492,17 @@ static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type dev descriptorPlayback.periodCount = deviceConfig.periods; } - result = ma_device_init__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); + result = ma_device_init_streams__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { - return result; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to create stream after route change."); + goto done; } result = ma_device_post_init(pDevice, deviceType, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { - ma_device_uninit__aaudio(pDevice); - return result; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to initialize device after route change."); + ma_close_streams__aaudio(pDevice); + goto done; } /* We'll only ever do this in response to a reroute. */ @@ -38204,14 +38511,29 @@ static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type dev /* If the device is started, start the streams. Maybe make this configurable? */ if (ma_device_get_state(pDevice) == ma_device_state_started) { if (pDevice->aaudio.noAutoStartAfterReroute == MA_FALSE) { - ma_device_start__aaudio(pDevice); + result = ma_device_start__aaudio(pDevice); + if (result != MA_SUCCESS) { + /* We got disconnected! Retry a few times, until we find a connected device! */ + retries += 1; + if (retries <= 3) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, retrying(%d)", retries); + goto error_disconnected; + } + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change."); + goto done; + } } else { ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ } } - - return MA_SUCCESS; + + result = MA_SUCCESS; } +done: + /* Re-routing done */ + ma_mutex_unlock(&pDevice->aaudio.rerouteLock); + + return result; } static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) @@ -38222,12 +38544,12 @@ static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type t MA_ASSERT(type != ma_device_type_duplex); MA_ASSERT(pDeviceInfo != NULL); - if (type == ma_device_type_playback) { + if (type == ma_device_type_capture) { pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamCapture; pDeviceInfo->id.aaudio = pDevice->capture.id.aaudio; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ } - if (type == ma_device_type_capture) { + if (type == ma_device_type_playback) { pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback; pDeviceInfo->id.aaudio = pDevice->playback.id.aaudio; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ @@ -38260,6 +38582,7 @@ static ma_result ma_context_uninit__aaudio(ma_context* pContext) static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { +#if !defined(MA_NO_RUNTIME_LINKING) size_t i; const char* libNames[] = { "libaaudio.so" @@ -38305,7 +38628,39 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getFramesPerBurst"); pContext->aaudio.AAudioStream_requestStart = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStart"); pContext->aaudio.AAudioStream_requestStop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStop"); - +#else + pContext->aaudio.AAudio_createStreamBuilder = (ma_proc)AAudio_createStreamBuilder; + pContext->aaudio.AAudioStreamBuilder_delete = (ma_proc)AAudioStreamBuilder_delete; + pContext->aaudio.AAudioStreamBuilder_setDeviceId = (ma_proc)AAudioStreamBuilder_setDeviceId; + pContext->aaudio.AAudioStreamBuilder_setDirection = (ma_proc)AAudioStreamBuilder_setDirection; + pContext->aaudio.AAudioStreamBuilder_setSharingMode = (ma_proc)AAudioStreamBuilder_setSharingMode; + pContext->aaudio.AAudioStreamBuilder_setFormat = (ma_proc)AAudioStreamBuilder_setFormat; + pContext->aaudio.AAudioStreamBuilder_setChannelCount = (ma_proc)AAudioStreamBuilder_setChannelCount; + pContext->aaudio.AAudioStreamBuilder_setSampleRate = (ma_proc)AAudioStreamBuilder_setSampleRate; + pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames = (ma_proc)AAudioStreamBuilder_setBufferCapacityInFrames; + pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback = (ma_proc)AAudioStreamBuilder_setFramesPerDataCallback; + pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)AAudioStreamBuilder_setDataCallback; + pContext->aaudio.AAudioStreamBuilder_setErrorCallback = (ma_proc)AAudioStreamBuilder_setErrorCallback; + pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)AAudioStreamBuilder_setPerformanceMode; + pContext->aaudio.AAudioStreamBuilder_setUsage = (ma_proc)AAudioStreamBuilder_setUsage; + pContext->aaudio.AAudioStreamBuilder_setContentType = (ma_proc)AAudioStreamBuilder_setContentType; + pContext->aaudio.AAudioStreamBuilder_setInputPreset = (ma_proc)AAudioStreamBuilder_setInputPreset; + #if defined(__ANDROID_API__) && __ANDROID_API__ >= 29 + pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy = (ma_proc)AAudioStreamBuilder_setAllowedCapturePolicy; + #endif + pContext->aaudio.AAudioStreamBuilder_openStream = (ma_proc)AAudioStreamBuilder_openStream; + pContext->aaudio.AAudioStream_close = (ma_proc)AAudioStream_close; + pContext->aaudio.AAudioStream_getState = (ma_proc)AAudioStream_getState; + pContext->aaudio.AAudioStream_waitForStateChange = (ma_proc)AAudioStream_waitForStateChange; + pContext->aaudio.AAudioStream_getFormat = (ma_proc)AAudioStream_getFormat; + pContext->aaudio.AAudioStream_getChannelCount = (ma_proc)AAudioStream_getChannelCount; + pContext->aaudio.AAudioStream_getSampleRate = (ma_proc)AAudioStream_getSampleRate; + pContext->aaudio.AAudioStream_getBufferCapacityInFrames = (ma_proc)AAudioStream_getBufferCapacityInFrames; + pContext->aaudio.AAudioStream_getFramesPerDataCallback = (ma_proc)AAudioStream_getFramesPerDataCallback; + pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)AAudioStream_getFramesPerBurst; + pContext->aaudio.AAudioStream_requestStart = (ma_proc)AAudioStream_requestStart; + pContext->aaudio.AAudioStream_requestStop = (ma_proc)AAudioStream_requestStop; +#endif pCallbacks->onContextInit = ma_context_init__aaudio; pCallbacks->onContextUninit = ma_context_uninit__aaudio; @@ -38343,6 +38698,7 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) { + ma_result result; ma_device* pDevice; MA_ASSERT(pJob != NULL); @@ -38351,7 +38707,18 @@ static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) MA_ASSERT(pDevice != NULL); /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ - return ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); + result = ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); + if (result != MA_SUCCESS) { + /* + Getting here means we failed to reroute the device. The best thing I can think of here is to + just stop the device. + */ + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to reroute failure."); + ma_device_stop(pDevice); + return result; + } + + return MA_SUCCESS; } #else /* Getting here means there is no AAudio backend so we need a no-op job implementation. */ @@ -39637,6 +40004,10 @@ Web Audio Backend #if (__EMSCRIPTEN_major__ > 3) || (__EMSCRIPTEN_major__ == 3 && (__EMSCRIPTEN_minor__ > 1 || (__EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 32))) #include #define MA_SUPPORT_AUDIO_WORKLETS + + #if (__EMSCRIPTEN_major__ > 3) || (__EMSCRIPTEN_major__ == 3 && (__EMSCRIPTEN_minor__ > 1 || (__EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 70))) + #define MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE + #endif #endif /* @@ -39648,7 +40019,7 @@ TODO: Version 0.12: Swap this logic around so that AudioWorklets are used by def /* The thread stack size must be a multiple of 16. */ #ifndef MA_AUDIO_WORKLETS_THREAD_STACK_SIZE -#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 16384 +#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 131072 #endif #if defined(MA_USE_AUDIO_WORKLETS) @@ -39774,12 +40145,14 @@ static ma_result ma_device_uninit__webaudio(ma_device* pDevice) #if defined(MA_USE_AUDIO_WORKLETS) { EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); if (device.streamNode !== undefined) { device.streamNode.disconnect(); device.streamNode = undefined; } + + device.pDevice = undefined; }, pDevice->webaudio.deviceIndex); emscripten_destroy_web_audio_node(pDevice->webaudio.audioWorklet); @@ -39789,7 +40162,7 @@ static ma_result ma_device_uninit__webaudio(ma_device* pDevice) #else { EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); /* Make sure all nodes are disconnected and marked for collection. */ if (device.scriptNode !== undefined) { @@ -39816,7 +40189,7 @@ static ma_result ma_device_uninit__webaudio(ma_device* pDevice) /* Clean up the device on the JS side. */ EM_ASM({ - miniaudio.untrack_device_by_index($0); + window.miniaudio.untrack_device_by_index($0); }, pDevice->webaudio.deviceIndex); ma_free(pDevice->webaudio.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); @@ -39882,10 +40255,6 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const (void)paramCount; (void)pParams; - if (ma_device_get_state(pDevice) != ma_device_state_started) { - return EM_TRUE; - } - /* The Emscripten documentation says that it'll always be 128 frames being passed in. Hard coding it like that feels like a very bad idea to me. Even if it's hard coded in the backend, the API and documentation should always refer @@ -39894,7 +40263,20 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const Unfortunately the audio data is not interleaved so we'll need to convert it before we give the data to miniaudio for further processing. */ - frameCount = 128; + if (pDevice->type == ma_device_type_playback) { + frameCount = pDevice->playback.internalPeriodSizeInFrames; + } else { + frameCount = pDevice->capture.internalPeriodSizeInFrames; + } + + if (ma_device_get_state(pDevice) != ma_device_state_started) { + /* Fill the output buffer with zero to avoid a noise sound */ + for (int i = 0; i < outputCount; i += 1) { + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); + } + + return EM_TRUE; + } if (inputCount > 0) { /* Input data needs to be interleaved before we hand it to the client. */ @@ -39949,7 +40331,7 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a count from MediaStreamAudioSourceNode (what we use for capture)? The only way to have control is to configure an output channel count on the capture side. This is slightly confusing for capture mode because intuitively you wouldn't actually connect an output to an input-only node, but this is what we'll have to do in order to have - proper control over the channel count. In the capture case, we'll have to output silence to it's output node. + proper control over the channel count. In the capture case, we'll have to output silence to its output node. */ if (pParameters->pConfig->deviceType == ma_device_type_capture) { channels = (int)((pParameters->pDescriptorCapture->channels > 0) ? pParameters->pDescriptorCapture->channels : MA_DEFAULT_CHANNELS); @@ -39972,7 +40354,15 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a Now that we know the channel count to use we can allocate the intermediary buffer. The intermediary buffer is used for interleaving and deinterleaving. */ - intermediaryBufferSizeInFrames = 128; + #if defined(MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE) + { + intermediaryBufferSizeInFrames = (size_t)emscripten_audio_context_quantum_size(audioContext); + } + #else + { + intermediaryBufferSizeInFrames = 128; + } + #endif pParameters->pDevice->webaudio.pIntermediaryBuffer = (float*)ma_malloc(intermediaryBufferSizeInFrames * (ma_uint32)channels * sizeof(float), &pParameters->pDevice->pContext->allocationCallbacks); if (pParameters->pDevice->webaudio.pIntermediaryBuffer == NULL) { @@ -39981,7 +40371,6 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a return; } - pParameters->pDevice->webaudio.audioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "miniaudio", &audioWorkletOptions, &ma_audio_worklet_process_callback__webaudio, pParameters->pDevice); /* With the audio worklet initialized we can now attach it to the graph. */ @@ -40121,7 +40510,6 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co /* It's not clear if this can return an error. None of the tests in the Emscripten repository check for this, so neither am I for now. */ pDevice->webaudio.audioContext = emscripten_create_audio_context(&audioContextAttributes); - /* With the context created we can now create the worklet. We can only have a single worklet per audio context which means we'll need to craft this appropriately to handle duplex devices correctly. @@ -40170,11 +40558,12 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co /* We need to add an entry to the miniaudio.devices list on the JS side so we can do some JS/C interop. */ pDevice->webaudio.deviceIndex = EM_ASM_INT({ - return miniaudio.track_device({ + return window.miniaudio.track_device({ webaudio: emscriptenGetAudioObject($0), - state: 1 /* 1 = ma_device_state_stopped */ + state: 1, /* 1 = ma_device_state_stopped */ + pDevice: $1 }); - }, pDevice->webaudio.audioContext); + }, pDevice->webaudio.audioContext, pDevice); return MA_SUCCESS; } @@ -40186,7 +40575,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co ma_uint32 sampleRate; ma_uint32 periodSizeInFrames; - /* The channel count will depend on the device type. If it's a capture, use it's, otherwise use the playback side. */ + /* The channel count will depend on the device type. If it's a capture, use its, otherwise use the playback side. */ if (pConfig->deviceType == ma_device_type_capture) { channels = (pDescriptorCapture->channels > 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; } else { @@ -40255,11 +40644,11 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co /* The node processing callback. */ device.scriptNode.onaudioprocess = function(e) { if (device.intermediaryBufferView == null || device.intermediaryBufferView.length == 0) { - device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, pIntermediaryBuffer, bufferSize * channels); + device.intermediaryBufferView = new Float32Array(HEAPF32.buffer, pIntermediaryBuffer, bufferSize * channels); } /* Do the capture side first. */ - if (deviceType == miniaudio.device_type.capture || deviceType == miniaudio.device_type.duplex) { + if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { /* The data must be interleaved before being processed miniaudio. */ for (var iChannel = 0; iChannel < channels; iChannel += 1) { var inputBuffer = e.inputBuffer.getChannelData(iChannel); @@ -40273,7 +40662,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co _ma_device_process_pcm_frames_capture__webaudio(pDevice, bufferSize, pIntermediaryBuffer); } - if (deviceType == miniaudio.device_type.playback || deviceType == miniaudio.device_type.duplex) { + if (deviceType == window.miniaudio.device_type.playback || deviceType == window.miniaudio.device_type.duplex) { _ma_device_process_pcm_frames_playback__webaudio(pDevice, bufferSize, pIntermediaryBuffer); for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { @@ -40293,7 +40682,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co }; /* Now we need to connect our node to the graph. */ - if (deviceType == miniaudio.device_type.capture || deviceType == miniaudio.device_type.duplex) { + if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { navigator.mediaDevices.getUserMedia({audio:true, video:false}) .then(function(stream) { device.streamNode = device.webaudio.createMediaStreamSource(stream); @@ -40305,13 +40694,13 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co }); } - if (deviceType == miniaudio.device_type.playback) { + if (deviceType == window.miniaudio.device_type.playback) { device.scriptNode.connect(device.webaudio.destination); } device.pDevice = pDevice; - return miniaudio.track_device(device); + return window.miniaudio.track_device(device); }, pConfig->deviceType, channels, sampleRate, periodSizeInFrames, pDevice->webaudio.pIntermediaryBuffer, pDevice); if (deviceIndex < 0) { @@ -40321,7 +40710,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co pDevice->webaudio.deviceIndex = deviceIndex; /* Grab the sample rate from the audio context directly. */ - sampleRate = (ma_uint32)EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + sampleRate = (ma_uint32)EM_ASM_INT({ return window.miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); if (pDescriptorCapture != NULL) { pDescriptorCapture->format = ma_format_f32; @@ -40351,9 +40740,9 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) MA_ASSERT(pDevice != NULL); EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = miniaudio.device_state.started; + device.state = window.miniaudio.device_state.started; }, pDevice->webaudio.deviceIndex); return MA_SUCCESS; @@ -40373,9 +40762,9 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) do any kind of explicit draining. */ EM_ASM({ - var device = miniaudio.get_device_by_index($0); + var device = window.miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = miniaudio.device_state.stopped; + device.state = window.miniaudio.device_state.stopped; }, pDevice->webaudio.deviceIndex); ma_device__on_notification_stopped(pDevice); @@ -40393,6 +40782,10 @@ static ma_result ma_context_uninit__webaudio(ma_context* pContext) /* Remove the global miniaudio object from window if there are no more references to it. */ EM_ASM({ if (typeof(window.miniaudio) !== 'undefined') { + miniaudio.unlock_event_types.map(function(event_type) { + document.removeEventListener(event_type, miniaudio.unlock, true); + }); + window.miniaudio.referenceCount -= 1; if (window.miniaudio.referenceCount === 0) { delete window.miniaudio; @@ -40434,6 +40827,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex window.miniaudio.device_state.started = $4; /* Device cache for mapping devices to indexes for JavaScript/C interop. */ + let miniaudio = window.miniaudio; miniaudio.devices = []; miniaudio.track_device = function(device) { @@ -40485,13 +40879,13 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex var device = miniaudio.devices[i]; if (device != null && device.webaudio != null && - device.state === window.miniaudio.device_state.started) { + device.state === miniaudio.device_state.started) { device.webaudio.resume().then(() => { - Module._ma_device__on_notification_unlocked(device.pDevice); - }, - (error) => {console.error("Failed to resume audiocontext", error); - }); + _ma_device__on_notification_unlocked(device.pDevice); + }, + (error) => {console.error("Failed to resume audiocontext", error); + }); } } miniaudio.unlock_event_types.map(function(event_type) { @@ -40527,7 +40921,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex return MA_SUCCESS; } -#endif /* Web Audio */ +#endif /* MA_HAS_WEBAUDIO */ @@ -40806,7 +41200,7 @@ MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceTy ma_device_info deviceInfo; if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - result = ma_device_get_info(pDevice, (deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); + result = ma_device_get_info(pDevice, ma_device_type_capture, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); } else { @@ -40853,7 +41247,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) #endif /* - When the device is being initialized it's initial state is set to ma_device_state_uninitialized. Before returning from + When the device is being initialized its initial state is set to ma_device_state_uninitialized. Before returning from ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker thread to signal an event to know when the worker thread is ready for action. @@ -41198,6 +41592,24 @@ MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_ } +MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB) +{ + size_t i; + + if (pA == NULL || pB == NULL) { + return MA_FALSE; + } + + for (i = 0; i < sizeof(ma_device_id); i += 1) { + if (((const char*)pA)[i] != ((const char*)pB)[i]) { + return MA_FALSE; + } + } + + return MA_TRUE; +} + + MA_API ma_context_config ma_context_config_init(void) { @@ -41971,7 +42383,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC return result; } - /* Wait for the worker thread to put the device into it's stopped state for real. */ + /* Wait for the worker thread to put the device into its stopped state for real. */ ma_event_wait(&pDevice->stopEvent); MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); } else { @@ -41997,7 +42409,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; - ma_device_get_name(pDevice, (pDevice->type == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, name, sizeof(name), NULL); + ma_device_get_name(pDevice, ma_device_type_capture, name, sizeof(name), NULL); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Capture"); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); @@ -42250,6 +42662,17 @@ MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_ if (type == ma_device_type_playback) { return ma_context_get_device_info(pDevice->pContext, type, pDevice->playback.pID, pDeviceInfo); } else { + /* + Here we're getting the capture side, which is the branch we'll be entering for a loopback + device, since loopback is capturing. However, if the device is using the default device ID, + it won't get the correct information because it'll think we're asking for the default + capture device, where in fact for loopback we want the default *playback* device. We'll do + a bit of a hack here to make sure we get the correct info. + */ + if (pDevice->type == ma_device_type_loopback && pDevice->capture.pID == NULL) { + type = ma_device_type_playback; + } + return ma_context_get_device_info(pDevice->pContext, type, pDevice->capture.pID, pDeviceInfo); } } @@ -42311,6 +42734,15 @@ MA_API ma_result ma_device_start(ma_device* pDevice) ma_mutex_lock(&pDevice->startStopLock); { + /* + We need to check again if the device is in a started state because it's possible for one thread to have started the device + while another was waiting on the mutex. + */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + ma_mutex_unlock(&pDevice->startStopLock); + return MA_SUCCESS; /* Already started. */ + } + /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); @@ -42371,6 +42803,15 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) ma_mutex_lock(&pDevice->startStopLock); { + /* + We need to check again if the device is in a stopped state because it's possible for one thread to have stopped the device + while another was waiting on the mutex. + */ + if (ma_device_get_state(pDevice) == ma_device_state_stopped) { + ma_mutex_unlock(&pDevice->startStopLock); + return MA_SUCCESS; /* Already stopped. */ + } + /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_started); @@ -42389,7 +42830,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) } else { /* Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If - the backend is implementing it's own audio thread loop we'll need to wake it up if required. Note that we need to make + the backend is implementing its own audio thread loop we'll need to wake it up if required. Note that we need to make sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. */ @@ -42506,6 +42947,15 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void return MA_INVALID_ARGS; } + /* + There is an assert deeper in the code that checks that frameCount > 0. Since this is a public facing + API we'll need to check for that here. I've had reports that AAudio can sometimes post a frame count + of 0. + */ + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pDevice->type == ma_device_type_duplex) { if (pInput != NULL) { ma_device__handle_duplex_callback_capture(pDevice, frameCount, pInput, &pDevice->duplexRB.rb); @@ -42580,7 +43030,7 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 return 0; } - return bufferSizeInFrames*1000 / sampleRate; + return (bufferSizeInFrames*1000 + (sampleRate - 1)) / sampleRate; } MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_milliseconds(ma_uint32 bufferSizeInMilliseconds, ma_uint32 sampleRate) @@ -47408,7 +47858,7 @@ static ma_result ma_bpf_get_heap_layout(const ma_bpf_config* pConfig, ma_bpf_hea return MA_INVALID_ARGS; } - bpf2Count = pConfig->channels / 2; + bpf2Count = pConfig->order / 2; pHeapLayout->sizeInBytes = 0; @@ -49466,7 +49916,7 @@ MA_API float ma_fader_get_current_volume(const ma_fader* pFader) } else if ((ma_uint64)pFader->cursorInFrames >= pFader->lengthInFrames) { /* Safe case because the < 0 case was checked above. */ return pFader->volumeEnd; } else { - /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */ + /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpolation between volumeBeg and volumeEnd based on our cursor position. */ return ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, (ma_uint32)pFader->cursorInFrames / (float)((ma_uint32)pFader->lengthInFrames)); /* Safe cast to uint32 because we clamp it in ma_fader_process_pcm_frames(). */ } } @@ -49689,9 +50139,9 @@ static float ma_attenuation_exponential(float distance, float minDistance, float /* -Dopper Effect calculation taken from the OpenAL spec, with two main differences: +Doppler Effect calculation taken from the OpenAL spec, with two main differences: - 1) The source to listener vector will have already been calcualted at an earlier step so we can + 1) The source to listener vector will have already been calculated at an earlier step so we can just use that directly. We need only the position of the source relative to the origin. 2) We don't scale by a frequency because we actually just want the ratio which we'll plug straight @@ -49730,7 +50180,7 @@ static void ma_get_default_channel_map_for_spatializer(ma_channel* pChannelMap, Special case for stereo. Want to default the left and right speakers to side left and side right so that they're facing directly down the X axis rather than slightly forward. Not doing this will result in sounds being quieter when behind the listener. This might - actually be good for some scenerios, but I don't think it's an appropriate default because + actually be good for some scenarios, but I don't think it's an appropriate default because it can be a bit unexpected. */ if (channelCount == 2) { @@ -50064,7 +50514,7 @@ MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma config.maxDistance = MA_FLT_MAX; config.rolloff = 1; config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ - config.coneOuterAngleInRadians = 6.283185f; /* 360 degress. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degrees. */ config.coneOuterGain = 0.0f; config.dopplerFactor = 1; config.directionalAttenuationFactor = 1; @@ -50298,7 +50748,7 @@ static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneI To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We just need to get the direction from the source to the listener and then do a dot product against that and the direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer - angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + angles. If the dot product is greater than the outer angle, we just use coneOuterGain. If it's less than the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. */ if (coneInnerAngleInRadians < 6.283185f) { @@ -50368,7 +50818,7 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_vec3f relativePosNormalized; ma_vec3f relativePos; /* The position relative to the listener. */ ma_vec3f relativeDir; /* The direction of the sound, relative to the listener. */ - ma_vec3f listenerVel; /* The volocity of the listener. For doppler pitch calculation. */ + ma_vec3f listenerVel; /* The velocity of the listener. For doppler pitch calculation. */ float speedOfSound; float distance = 0; float gain = 1; @@ -50449,11 +50899,11 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We just need to get the direction from the source to the listener and then do a dot product against that and the direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer - angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + angles. If the dot product is greater than the outer angle, we just use coneOuterGain. If it's less than the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. */ if (distance > 0) { - /* Source anglular gain. */ + /* Source angular gain. */ float spatializerConeInnerAngle; float spatializerConeOuterAngle; float spatializerConeOuterGain; @@ -50965,7 +51415,7 @@ MA_API void ma_spatializer_get_relative_position_and_direction(const ma_spatiali listenerDirection = ma_spatializer_listener_get_direction(pListener); /* - We need to calcualte the right vector from our forward and up vectors. This is done with + We need to calculate the right vector from our forward and up vectors. This is done with a cross product. */ axisZ = ma_vec3f_normalize(listenerDirection); /* Normalization required here because we can't trust the caller. */ @@ -51111,7 +51561,7 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes lpfConfig = ma_lpf_config_init(pResampler->config.format, pResampler->config.channels, lpfSampleRate, lpfCutoffFrequency, pResampler->config.lpfOrder); /* - If the resampler is alreay initialized we don't want to do a fresh initialization of the low-pass filter because it will result in the cached frames + If the resampler is already initialized we don't want to do a fresh initialization of the low-pass filter because it will result in the cached frames getting cleared. Instead we re-initialize the filter which will maintain any cached frames. */ if (isResamplerAlreadyInitialized) { @@ -51806,7 +52256,7 @@ MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_li preliminaryInputFrameCount = (pResampler->inTimeInt + outputFrameCount*pResampler->inAdvanceInt ) + preliminaryInputFrameCountFromFrac; /* - If the total number of *whole* input frames that would be required to generate our preliminary output frame count is greather than + If the total number of *whole* input frames that would be required to generate our preliminary output frame count is greater than the amount of whole input frames we have available as input we need to *not* add an extra output frame as there won't be enough data to actually process. Otherwise we need to add the extra output frame. */ @@ -51844,7 +52294,7 @@ MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) } } - /* The low pass filter needs to have it's cache reset. */ + /* The low pass filter needs to have its cache reset. */ ma_lpf_clear_cache(&pResampler->lpf); return MA_SUCCESS; @@ -52361,19 +52811,19 @@ static float ma_calculate_channel_position_rectangular_weight(ma_channel channel of contribution to apply to the side/left and back/left speakers, however, is a bit more complicated. Imagine the front/left speaker as emitting audio from two planes - the front plane and the left plane. You can think of the front/left - speaker emitting half of it's total volume from the front, and the other half from the left. Since part of it's volume is being emitted + speaker emitting half of its total volume from the front, and the other half from the left. Since part of its volume is being emitted from the left side, and the side/left and back/left channels also emit audio from the left plane, one would expect that they would receive some amount of contribution from front/left speaker. The amount of contribution depends on how many planes are shared between the two speakers. Note that in the examples below I've added a top/front/left speaker as an example just to show how the math works across 3 spatial dimensions. The first thing to do is figure out how each speaker's volume is spread over each of plane: - - front/left: 2 planes (front and left) = 1/2 = half it's total volume on each plane + - front/left: 2 planes (front and left) = 1/2 = half its total volume on each plane - side/left: 1 plane (left only) = 1/1 = entire volume from left plane - - back/left: 2 planes (back and left) = 1/2 = half it's total volume on each plane - - top/front/left: 3 planes (top, front and left) = 1/3 = one third it's total volume on each plane + - back/left: 2 planes (back and left) = 1/2 = half its total volume on each plane + - top/front/left: 3 planes (top, front and left) = 1/3 = one third its total volume on each plane - The amount of volume each channel contributes to each of it's planes is what controls how much it is willing to given and take to other + The amount of volume each channel contributes to each of its planes is what controls how much it is willing to given and take to other channels on the same plane. The volume that is willing to the given by one channel is multiplied by the volume that is willing to be taken by the other to produce the final contribution. */ @@ -52484,12 +52934,7 @@ static ma_channel_conversion_path ma_channel_map_get_conversion_path(const ma_ch ma_uint32 iChannelIn; ma_bool32 areAllChannelPositionsPresent = MA_TRUE; for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) { - ma_bool32 isInputChannelPositionInOutput = MA_FALSE; - if (ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn))) { - isInputChannelPositionInOutput = MA_TRUE; - break; - } - + ma_bool32 isInputChannelPositionInOutput = ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn)); if (!isInputChannelPositionInOutput) { areAllChannelPositionsPresent = MA_FALSE; break; @@ -52516,8 +52961,8 @@ static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMa } /* - When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the - input channel has more than one occurance of a channel position, the second one will be ignored. + When building the shuffle table we just do a 1:1 mapping based on the first occurrence of a channel. If the + input channel has more than one occurrence of a channel position, the second one will be ignored. */ for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) { ma_channel channelOut; @@ -54812,7 +55257,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co Before doing any processing we need to determine how many frames we should try processing this iteration, for both input and output. The resampler requires us to perform format and channel conversion before passing any data into it. If we get our input count wrong, we'll - end up peforming redundant pre-processing. This isn't the end of the world, but it does + end up performing redundant pre-processing. This isn't the end of the world, but it does result in some inefficiencies proportionate to how far our estimates are off. If the resampler has a means to calculate exactly how much we'll need, we'll use that. @@ -55982,7 +56427,7 @@ MA_API const char* ma_channel_position_to_string(ma_channel channel) case MA_CHANNEL_LFE : return "CHANNEL_LFE"; case MA_CHANNEL_BACK_LEFT : return "CHANNEL_BACK_LEFT"; case MA_CHANNEL_BACK_RIGHT : return "CHANNEL_BACK_RIGHT"; - case MA_CHANNEL_FRONT_LEFT_CENTER : return "CHANNEL_FRONT_LEFT_CENTER "; + case MA_CHANNEL_FRONT_LEFT_CENTER : return "CHANNEL_FRONT_LEFT_CENTER"; case MA_CHANNEL_FRONT_RIGHT_CENTER: return "CHANNEL_FRONT_RIGHT_CENTER"; case MA_CHANNEL_BACK_CENTER : return "CHANNEL_BACK_CENTER"; case MA_CHANNEL_SIDE_LEFT : return "CHANNEL_SIDE_LEFT"; @@ -56287,13 +56732,9 @@ MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes) newReadOffsetLoopFlag ^= 0x80000000; } - ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetLoopFlag, newReadOffsetInBytes)); + ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetInBytes, newReadOffsetLoopFlag)); - if (ma_rb_pointer_distance(pRB) == 0) { - return MA_AT_END; - } else { - return MA_SUCCESS; - } + return MA_SUCCESS; } MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) @@ -56373,13 +56814,9 @@ MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes) newWriteOffsetLoopFlag ^= 0x80000000; } - ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetLoopFlag, newWriteOffsetInBytes)); + ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetInBytes, newWriteOffsetLoopFlag)); - if (ma_rb_pointer_distance(pRB) == 0) { - return MA_AT_END; - } else { - return MA_SUCCESS; - } + return MA_SUCCESS; } MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes) @@ -56602,6 +57039,16 @@ static ma_result ma_pcm_rb_data_source__on_read(ma_data_source* pDataSource, voi totalFramesRead += mappedFrameCount; } + /* + There is no notion of an "end" in a ring buffer. If we didn't have enough data to fill the requested frame + count we'll need to pad with silence. If we don't do this, totalFramesRead might equal 0 which will result + in the data source layer at a higher level translating this to MA_AT_END which is incorrect for a ring buffer. + */ + if (totalFramesRead < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, pRB->format, pRB->channels), (frameCount - totalFramesRead), pRB->format, pRB->channels); + totalFramesRead = frameCount; + } + *pFramesRead = totalFramesRead; return MA_SUCCESS; } @@ -57150,6 +57597,10 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da return MA_INVALID_ARGS; } + if (pConfig->vtable == NULL) { + return MA_INVALID_ARGS; + } + pDataSourceBase->vtable = pConfig->vtable; pDataSourceBase->rangeBegInFrames = MA_DATA_SOURCE_DEFAULT_RANGE_BEG; pDataSourceBase->rangeEndInFrames = MA_DATA_SOURCE_DEFAULT_RANGE_END; @@ -57200,6 +57651,58 @@ static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_ return MA_SUCCESS; } +static ma_result ma_data_source_read_pcm_frames_from_backend(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + MA_ASSERT(pDataSourceBase != NULL); + MA_ASSERT(pDataSourceBase->vtable != NULL); + MA_ASSERT(pDataSourceBase->vtable->onRead != NULL); + MA_ASSERT(pFramesRead != NULL); + + if (pFramesOut != NULL) { + return pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); + } else { + /* + No output buffer. Probably seeking forward. Read and discard. Can probably optimize this in terms of + onSeek and onGetCursor, but need to keep in mind that the data source may not implement these functions. + */ + ma_result result; + ma_uint64 framesRead; + ma_format format; + ma_uint32 channels; + ma_uint64 discardBufferCapInFrames; + ma_uint8 pDiscardBuffer[4096]; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + discardBufferCapInFrames = sizeof(pDiscardBuffer) / ma_get_bytes_per_frame(format, channels); + + framesRead = 0; + while (framesRead < frameCount) { + ma_uint64 framesReadThisIteration = 0; + ma_uint64 framesToRead = frameCount - framesRead; + if (framesToRead > discardBufferCapInFrames) { + framesToRead = discardBufferCapInFrames; + } + + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pDiscardBuffer, framesToRead, &framesReadThisIteration); + if (result != MA_SUCCESS) { + return result; + } + + framesRead += framesReadThisIteration; + } + + *pFramesRead = framesRead; + + return MA_SUCCESS; + } +} + static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -57215,9 +57718,11 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa return MA_INVALID_ARGS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + if ((pDataSourceBase->vtable->flags & MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT) != 0 || (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { /* Either the data source is self-managing the range, or no range is set - just read like normal. The data source itself will tell us when the end is reached. */ - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { /* Need to clamp to within the range. */ ma_uint64 relativeCursor; @@ -57226,7 +57731,7 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa result = ma_data_source_get_cursor_in_pcm_frames(pDataSourceBase, &relativeCursor); if (result != MA_SUCCESS) { /* Failed to retrieve the cursor. Cannot read within a range or loop points. Just read like normal - this may happen for things like noise data sources where it doesn't really matter. */ - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { ma_uint64 rangeBeg; ma_uint64 rangeEnd; @@ -57254,7 +57759,7 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa MA_AT_END so the higher level function can know about it. */ if (frameCount > 0) { - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { result = MA_AT_END; /* The cursor is sitting on the end of the range which means we're at the end. */ } @@ -57336,7 +57841,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi totalFramesProcessed += framesProcessed; /* - If we encounted an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is + If we encountered an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is not necessarily considered an error. */ if (result != MA_SUCCESS && result != MA_AT_END) { @@ -57427,7 +57932,7 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pDataSourceBase == NULL) { - return MA_SUCCESS; + return MA_INVALID_ARGS; } if (pDataSourceBase->vtable->onSeek == NULL) { @@ -57435,12 +57940,61 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m } if (frameIndex > pDataSourceBase->rangeEndInFrames) { - return MA_INVALID_OPERATION; /* Trying to seek to far forward. */ + return MA_INVALID_OPERATION; /* Trying to seek too far forward. */ } + MA_ASSERT(pDataSourceBase->vtable != NULL); + return pDataSourceBase->vtable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); } +MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked) +{ + ma_uint64 frameCount; + ma_uint64 framesSeeked = 0; + ma_uint32 sampleRate; + ma_result result; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* We need PCM frames instead of seconds */ + frameCount = (ma_uint64)(secondCount * sampleRate); + + result = ma_data_source_seek_pcm_frames(pDataSource, frameCount, &framesSeeked); + + /* VC6 doesn't support division between unsigned 64-bit integer and floating point number. Signed integer needed. This shouldn't affect anything in practice */ + *pSecondsSeeked = (ma_int64)framesSeeked / (float)sampleRate; + return result; +} + +MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds) +{ + ma_uint64 frameIndex; + ma_uint32 sampleRate; + ma_result result; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* We need PCM frames instead of seconds */ + frameIndex = (ma_uint64)(seekPointInSeconds * sampleRate); + + return ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); +} + MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -57467,6 +58021,8 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ return MA_INVALID_ARGS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + if (pDataSourceBase->vtable->onGetDataFormat == NULL) { return MA_NOT_IMPLEMENTED; } @@ -57507,6 +58063,8 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo return MA_SUCCESS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + if (pDataSourceBase->vtable->onGetCursor == NULL) { return MA_NOT_IMPLEMENTED; } @@ -57540,6 +58098,8 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo return MA_INVALID_ARGS; } + MA_ASSERT(pDataSourceBase->vtable != NULL); + /* If we have a range defined we'll use that to determine the length. This is one of rare times where we'll actually trust the caller. If they've set the range, I think it's mostly safe to @@ -57627,6 +58187,8 @@ MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool ma_atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); + MA_ASSERT(pDataSourceBase->vtable != NULL); + /* If there's no callback for this just treat it as a successful no-op. */ if (pDataSourceBase->vtable->onSetLooping == NULL) { return MA_SUCCESS; @@ -57664,7 +58226,7 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou /* We may need to adjust the position of the cursor to ensure it's clamped to the range. Grab it now - so we can calculate it's absolute position before we change the range. + so we can calculate its absolute position before we change the range. */ result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &relativeCursor); if (result == MA_SUCCESS) { @@ -57698,7 +58260,7 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou /* Seek to within range. Note that our seek positions here are relative to the new range. We don't want - do do this if we failed to retrieve the cursor earlier on because it probably means the data source + to do this if we failed to retrieve the cursor earlier on because it probably means the data source has no notion of a cursor. In practice the seek would probably fail (which we silently ignore), but I'm just not even going to attempt it. */ @@ -57717,6 +58279,13 @@ MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSo { const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + if (pRangeBegInFrames != NULL) { + *pRangeBegInFrames = 0; + } + if (pRangeEndInFrames != NULL) { + *pRangeEndInFrames = 0; + } + if (pDataSource == NULL) { return; } @@ -57761,6 +58330,13 @@ MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pD { const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + if (pLoopBegInFrames != NULL) { + *pLoopBegInFrames = 0; + } + if (pLoopEndInFrames != NULL) { + *pLoopEndInFrames = 0; + } + if (pDataSource == NULL) { return; } @@ -59155,7 +59731,7 @@ static ma_result ma_default_vfs_seek__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i result = ma_SetFilePointerEx((HANDLE)file, liDistanceToMove, NULL, dwMoveMethod); } else if (ma_SetFilePointer != NULL) { /* No SetFilePointerEx() so restrict to 31 bits. */ - if (origin > 0x7FFFFFFF) { + if (offset > 0x7FFFFFFF) { return MA_OUT_OF_RANGE; } @@ -59365,7 +59941,7 @@ static ma_result ma_default_vfs_seek__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_i result = _fseeki64((FILE*)file, offset, whence); #else /* No _fseeki64() so restrict to 31 bits. */ - if (origin > 0x7FFFFFFF) { + if (offset > 0x7FFFFFFF) { return MA_OUT_OF_RANGE; } @@ -59758,7 +60334,7 @@ extern "C" { #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MINOR 13 -#define MA_DR_WAV_VERSION_REVISION 13 +#define MA_DR_WAV_VERSION_REVISION 18 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -60178,7 +60754,7 @@ extern "C" { #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 #define MA_DR_FLAC_VERSION_MINOR 12 -#define MA_DR_FLAC_VERSION_REVISION 42 +#define MA_DR_FLAC_VERSION_REVISION 43 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -60465,7 +61041,7 @@ extern "C" { #define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x) #define MA_DR_MP3_VERSION_MAJOR 0 #define MA_DR_MP3_VERSION_MINOR 6 -#define MA_DR_MP3_VERSION_REVISION 38 +#define MA_DR_MP3_VERSION_REVISION 40 #define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION) #include #define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 @@ -60627,7 +61203,7 @@ MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint3 return config; } -MA_API ma_decoder_config ma_decoder_config_init_default() +MA_API ma_decoder_config ma_decoder_config_init_default(void) { return ma_decoder_config_init(ma_format_unknown, 0, 0); } @@ -63220,7 +63796,7 @@ MA_API ma_result ma_stbvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_ #if !defined(MA_NO_VORBIS) { /* - stb_vorbis lacks a callback based API for it's pulling API which means we're stuck with the + stb_vorbis lacks a callback based API for its pulling API which means we're stuck with the pushing API. In order for us to be able to successfully initialize the decoder we need to supply it with enough data. We need to keep loading data until we have enough. */ @@ -63301,7 +63877,7 @@ MA_API ma_result ma_stbvorbis_init_memory(const void* pData, size_t dataSize, co { (void)pAllocationCallbacks; - /* stb_vorbis uses an int as it's size specifier, restricting it to 32-bit even on 64-bit systems. *sigh*. */ + /* stb_vorbis uses an int as its size specifier, restricting it to 32-bit even on 64-bit systems. *sigh*. */ if (dataSize > INT_MAX) { return MA_TOO_BIG; } @@ -63391,7 +63967,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram /* The first thing to do is read from any already-cached frames. */ ma_uint32 framesToReadFromCache = (ma_uint32)ma_min(pVorbis->push.framesRemaining, (frameCount - totalFramesRead)); /* Safe cast because pVorbis->framesRemaining is 32-bit. */ - /* The output pointer can be null in which case we just treate it as a seek. */ + /* The output pointer can be null in which case we just treat it as a seek. */ if (pFramesOut != NULL) { ma_uint64 iFrame; for (iFrame = 0; iFrame < framesToReadFromCache; iFrame += 1) { @@ -63465,7 +64041,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram } } - /* If we don't have a success code at this point it means we've encounted an error or the end of the file has been reached (probably the latter). */ + /* If we don't have a success code at this point it means we've encountered an error or the end of the file has been reached (probably the latter). */ if (result != MA_SUCCESS) { break; } @@ -64279,8 +64855,7 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons #if defined(MA_HAS_WAV) || \ defined(MA_HAS_MP3) || \ defined(MA_HAS_FLAC) || \ - defined(MA_HAS_VORBIS) || \ - defined(MA_HAS_OPUS) + defined(MA_HAS_VORBIS) #define MA_HAS_PATH_API #endif @@ -65095,7 +65670,7 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO } else { /* Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we - need to run through each sample because we need to ensure it's internal cache is updated. + need to run through each sample because we need to ensure its internal cache is updated. */ if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut); @@ -65185,8 +65760,17 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO if (requiredInputFrameCount > 0) { result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); + + /* + Note here that even if we've reached the end, we don't want to abort because there might be more output frames needing to be + generated from cached input data, which might happen if resampling is being performed. + */ + if (result != MA_SUCCESS && result != MA_AT_END) { + break; + } } else { framesReadThisIterationIn = 0; + pIntermediaryBuffer[0] = 0; /* <-- This is just to silence a static analysis warning. */ } /* @@ -66667,7 +67251,7 @@ MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type) /* This function should never have been implemented in the first place. Changing the type dynamically is not - supported. Instead you need to uninitialize and reinitiailize a fresh `ma_noise` object. This function + supported. Instead you need to uninitialize and reinitialize a fresh `ma_noise` object. This function will be removed in version 0.12. */ MA_ASSERT(MA_FALSE); @@ -67713,7 +68297,7 @@ MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pCon pResourceManager->config.pVFS = &pResourceManager->defaultVFS; } - /* If threading has been disabled at compile time, enfore it at run time as well. */ + /* If threading has been disabled at compile time, enforce it at run time as well. */ #ifdef MA_NO_THREADING { pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; @@ -67750,15 +68334,17 @@ MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pCon /* Custom decoding backends. */ if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) { size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount; + ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; - pResourceManager->config.ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); + ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) { ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); return MA_OUT_OF_MEMORY; } - MA_COPY_MEMORY(pResourceManager->config.ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + MA_COPY_MEMORY(ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + pResourceManager->config.ppCustomDecodingBackendVTables = ppCustomDecodingBackendVTables; pResourceManager->config.customDecodingBackendCount = pConfig->customDecodingBackendCount; pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData; } @@ -67809,7 +68395,7 @@ static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode; ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); - /* The data buffer has been removed from the BST, so now we need to free it's data. */ + /* The data buffer has been removed from the BST, so now we need to free its data. */ ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); } } @@ -67822,7 +68408,7 @@ MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) /* Job threads need to be killed first. To do this we need to post a quit message to the message queue and then wait for the thread. The quit message will never be removed from the - queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it. + queue which means it will never not be returned after being encountered for the first time which means all threads will eventually receive it. */ ma_resource_manager_post_job_quit(pResourceManager); @@ -67862,7 +68448,7 @@ MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) #endif } - ma_free(pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); + ma_free((ma_decoding_backend_vtable**)pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); /* <-- Naughty const-cast, but this is safe. */ if (pResourceManager->config.pLog == &pResourceManager->log) { ma_log_uninit(&pResourceManager->log); @@ -68280,7 +68866,7 @@ static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resour } result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead); - if (framesRead > 0) { + if (result == MA_SUCCESS && framesRead > 0) { pPage->sizeInFrames = framesRead; result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage); @@ -68433,7 +69019,7 @@ static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(m if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { ma_resource_manager_inline_notification_uninit(pInitNotification); } else { - /* These will have been freed by the job thread, but with WAIT_INIT they will already have happend sinced the job has already been handled. */ + /* These will have been freed by the job thread, but with WAIT_INIT they will already have happened since the job has already been handled. */ ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); } @@ -68798,6 +69384,10 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; } + if (pConfig->isLooping) { + flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } + async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0; /* @@ -68810,7 +69400,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma These fences are always released at the "done" tag at the end of this function. They'll be acquired a second if loading asynchronously. This double acquisition system is just done to - simplify code maintanence. + simplify code maintenance. */ ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); { @@ -68855,7 +69445,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma /* The status of the data buffer needs to be set to MA_BUSY before posting the job so that the - worker thread is aware of it's busy state. If the LOAD_DATA_BUFFER job sees a status other + worker thread is aware of its busy state. If the LOAD_DATA_BUFFER job sees a status other than MA_BUSY, it'll assume an error and fall through to an early exit. */ ma_atomic_exchange_i32(&pDataBuffer->result, MA_BUSY); @@ -68874,7 +69464,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma job.data.resourceManager.loadDataBuffer.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; job.data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; job.data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; - job.data.resourceManager.loadDataBuffer.isLooping = pConfig->isLooping; + job.data.resourceManager.loadDataBuffer.isLooping = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0; /* If we need to wait for initialization to complete we can just process the job in place. */ if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { @@ -69095,22 +69685,29 @@ MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_man isDecodedBufferBusy = (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY); if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) { - /* Don't try reading more than the available frame count. */ - if (frameCount > availableFrames) { - frameCount = availableFrames; + /* Don't try reading more than the available frame count if the data buffer node is still loading. */ + if (isDecodedBufferBusy) { + if (frameCount > availableFrames) { + frameCount = availableFrames; - /* - If there's no frames available we want to set the status to MA_AT_END. The logic below - will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this - is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count - is 0 because that'll result in a situation where it's possible MA_AT_END won't get - returned. - */ - if (frameCount == 0) { - result = MA_AT_END; + /* + If there's no frames available we want to set the status to MA_AT_END. The logic below + will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this + is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count + is 0 because that'll result in a situation where it's possible MA_AT_END won't get + returned. + */ + if (frameCount == 0) { + result = MA_AT_END; + } + } else { + isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ } } else { - isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ + /* + Getting here means the buffer has been fully loaded. We can just pass the frame count straight + into ma_data_source_read_pcm_frames() below and let ma_data_source handle it. + */ } } } @@ -69510,6 +70107,7 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR ma_bool32 waitBeforeReturning = MA_FALSE; ma_resource_manager_inline_notification waitNotification; ma_resource_manager_pipeline_notifications notifications; + ma_uint32 flags; if (pDataStream == NULL) { if (pConfig != NULL && pConfig->pNotifications != NULL) { @@ -69540,13 +70138,18 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR return result; } + flags = pConfig->flags; + if (pConfig->isLooping) { + flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } + pDataStream->pResourceManager = pResourceManager; pDataStream->flags = pConfig->flags; pDataStream->result = MA_BUSY; ma_data_source_set_range_in_pcm_frames(pDataStream, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); ma_data_source_set_loop_point_in_pcm_frames(pDataStream, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); - ma_data_source_set_looping(pDataStream, pConfig->isLooping); + ma_data_source_set_looping(pDataStream, (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0); if (pResourceManager == NULL || (pConfig->pFilePath == NULL && pConfig->pFilePathW == NULL)) { ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); @@ -70168,6 +70771,9 @@ static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pR } pDataSource->flags = pConfig->flags; + if (pConfig->isLooping) { + pDataSource->flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } return MA_SUCCESS; } @@ -70726,9 +71332,10 @@ static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob */ result = ma_resource_manager_data_buffer_result(pDataBuffer); if (result != MA_BUSY) { - goto done; /* <-- This will ensure the exucution pointer is incremented. */ + goto done; /* <-- This will ensure the execution pointer is incremented. */ } else { result = MA_SUCCESS; /* <-- Make sure this is reset. */ + (void)result; /* <-- This is to suppress a static analysis diagnostic about "result" not being used. But for safety when I do future maintenance I don't want to delete that assignment. */ } /* Try initializing the connector if we haven't already. */ @@ -71075,11 +71682,74 @@ static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob #ifndef MA_NO_NODE_GRAPH + +static ma_stack* ma_stack_init(size_t sizeInBytes, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_stack* pStack; + + if (sizeInBytes == 0) { + return NULL; + } + + pStack = (ma_stack*)ma_malloc(sizeof(*pStack) - sizeof(pStack->_data) + sizeInBytes, pAllocationCallbacks); + if (pStack == NULL) { + return NULL; + } + + pStack->offset = 0; + pStack->sizeInBytes = sizeInBytes; + + return pStack; +} + +static void ma_stack_uninit(ma_stack* pStack, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pStack == NULL) { + return; + } + + ma_free(pStack, pAllocationCallbacks); +} + +static void* ma_stack_alloc(ma_stack* pStack, size_t sz) +{ + /* The size of the allocation is stored in the memory directly before the pointer. This needs to include padding to keep it aligned to ma_uintptr */ + void* p = (void*)((char*)pStack->_data + pStack->offset); + size_t* pSize = (size_t*)p; + + sz = (sz + (sizeof(ma_uintptr) - 1)) & ~(sizeof(ma_uintptr) - 1); /* Padding. */ + if (pStack->offset + sz + sizeof(size_t) > pStack->sizeInBytes) { + return NULL; /* Out of memory. */ + } + + pStack->offset += sz + sizeof(size_t); + + *pSize = sz; + return (void*)((char*)p + sizeof(size_t)); +} + +static void ma_stack_free(ma_stack* pStack, void* p) +{ + size_t* pSize; + + if (p == NULL) { + return; + } + + pSize = (size_t*)p - 1; + pStack->offset -= *pSize + sizeof(size_t); +} + + + /* 10ms @ 48K = 480. Must never exceed 65535. */ #ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS #define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480 #endif +#ifndef MA_DEFAULT_PREMIX_STACK_SIZE_PER_CHANNEL +#define MA_DEFAULT_PREMIX_STACK_SIZE_PER_CHANNEL 524288 +#endif static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); @@ -71119,8 +71789,8 @@ MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels) ma_node_graph_config config; MA_ZERO_OBJECT(&config); - config.channels = channels; - config.nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + config.channels = channels; + config.processingSizeInFrames = 0; return config; } @@ -71207,11 +71877,7 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m } MA_ZERO_OBJECT(pNodeGraph); - pNodeGraph->nodeCacheCapInFrames = pConfig->nodeCacheCapInFrames; - if (pNodeGraph->nodeCacheCapInFrames == 0) { - pNodeGraph->nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; - } - + pNodeGraph->processingSizeInFrames = pConfig->processingSizeInFrames; /* Base node so we can use the node graph as a node into another graph. */ baseConfig = ma_node_config_init(); @@ -71236,6 +71902,40 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m return result; } + + /* Processing cache. */ + if (pConfig->processingSizeInFrames > 0) { + pNodeGraph->pProcessingCache = (float*)ma_malloc(pConfig->processingSizeInFrames * pConfig->channels * sizeof(float), pAllocationCallbacks); + if (pNodeGraph->pProcessingCache == NULL) { + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + + + /* + We need a pre-mix stack. The size of this stack is configurable via the config. The default value depends on the channel count. + */ + { + size_t preMixStackSizeInBytes = pConfig->preMixStackSizeInBytes; + if (preMixStackSizeInBytes == 0) { + preMixStackSizeInBytes = pConfig->channels * MA_DEFAULT_PREMIX_STACK_SIZE_PER_CHANNEL; + } + + pNodeGraph->pPreMixStack = ma_stack_init(preMixStackSizeInBytes, pAllocationCallbacks); + if (pNodeGraph->pPreMixStack == NULL) { + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + if (pNodeGraph->pProcessingCache != NULL) { + ma_free(pNodeGraph->pProcessingCache, pAllocationCallbacks); + } + + return MA_OUT_OF_MEMORY; + } + } + + return MA_SUCCESS; } @@ -71246,6 +71946,17 @@ MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_ } ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + + if (pNodeGraph->pProcessingCache != NULL) { + ma_free(pNodeGraph->pProcessingCache, pAllocationCallbacks); + pNodeGraph->pProcessingCache = NULL; + } + + if (pNodeGraph->pPreMixStack != NULL) { + ma_stack_uninit(pNodeGraph->pPreMixStack, pAllocationCallbacks); + pNodeGraph->pPreMixStack = NULL; + } } MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph) @@ -71278,27 +71989,72 @@ MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* totalFramesRead = 0; while (totalFramesRead < frameCount) { ma_uint32 framesJustRead; - ma_uint64 framesToRead = frameCount - totalFramesRead; + ma_uint64 framesToRead; + float* pRunningFramesOut; + framesToRead = frameCount - totalFramesRead; if (framesToRead > 0xFFFFFFFF) { framesToRead = 0xFFFFFFFF; } - ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); - { - result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); - } - ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + pRunningFramesOut = (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels); - totalFramesRead += framesJustRead; + /* If there's anything in the cache, consume that first. */ + if (pNodeGraph->processingCacheFramesRemaining > 0) { + ma_uint32 framesToReadFromCache; - if (result != MA_SUCCESS) { - break; - } + framesToReadFromCache = (ma_uint32)framesToRead; + if (framesToReadFromCache > pNodeGraph->processingCacheFramesRemaining) { + framesToReadFromCache = pNodeGraph->processingCacheFramesRemaining; + } - /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ - if (framesJustRead == 0) { - break; + MA_COPY_MEMORY(pRunningFramesOut, pNodeGraph->pProcessingCache, framesToReadFromCache * channels * sizeof(float)); + MA_MOVE_MEMORY(pNodeGraph->pProcessingCache, pNodeGraph->pProcessingCache + (framesToReadFromCache * channels), (pNodeGraph->processingCacheFramesRemaining - framesToReadFromCache) * channels * sizeof(float)); + pNodeGraph->processingCacheFramesRemaining -= framesToReadFromCache; + + totalFramesRead += framesToReadFromCache; + continue; + } else { + /* + If processingSizeInFrames is non-zero, we need to make sure we always read in chunks of that size. If the frame count is less than + that, we need to read into the cache and then continue on. + */ + float* pReadDst = pRunningFramesOut; + + if (pNodeGraph->processingSizeInFrames > 0) { + if (framesToRead < pNodeGraph->processingSizeInFrames) { + pReadDst = pNodeGraph->pProcessingCache; /* We need to read into the cache because otherwise we'll overflow the output buffer. */ + } + + framesToRead = pNodeGraph->processingSizeInFrames; + } + + ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); + { + result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, pReadDst, (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); + } + ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + + /* + Do not increment the total frames read counter if we read into the cache. We use this to determine how many frames have + been written to the final output buffer. + */ + if (pReadDst == pNodeGraph->pProcessingCache) { + /* We read into the cache. */ + pNodeGraph->processingCacheFramesRemaining = framesJustRead; + } else { + /* We read straight into the output buffer. */ + totalFramesRead += framesJustRead; + } + + if (result != MA_SUCCESS) { + break; + } + + /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ + if (framesJustRead == 0) { + break; + } } } @@ -71499,7 +72255,7 @@ static void ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus* pInp *not* using a lock when iterating over the list in the audio thread. We therefore need to craft this in a way such that the iteration on the audio thread doesn't break. - The the first thing to do is swap out the "next" pointer of the previous output bus with the + The first thing to do is swap out the "next" pointer of the previous output bus with the new "next" output bus. This is the operation that matters for iteration on the audio thread. After that, the previous pointer on the new "next" pointer needs to be updated, after which point the linked list will be in a good state. @@ -71592,7 +72348,7 @@ static void ma_node_input_bus_attach(ma_node_input_bus* pInputBus, ma_node_outpu /* Now we need to attach the output bus to the linked list. This involves updating two pointers on two different output buses so I'm going to go ahead and keep this simple and just use a lock. - There are ways to do this without a lock, but it's just too hard to maintain for it's value. + There are ways to do this without a lock, but it's just too hard to maintain for its value. Although we're locking here, it's important to remember that we're *not* locking when iterating and reading audio data since that'll be running on the audio thread. As a result we need to be @@ -71685,11 +72441,9 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ ma_uint32 inputChannels; ma_bool32 doesOutputBufferHaveContent = MA_FALSE; - (void)pInputNode; /* Not currently used. */ - /* This will be called from the audio thread which means we can't be doing any locking. Basically, - this function will not perfom any locking, whereas attaching and detaching will, but crafted in + this function will not perform any locking, whereas attaching and detaching will, but crafted in such a way that we don't need to perform any locking here. The important thing to remember is to always iterate in a forward direction. @@ -71735,19 +72489,12 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ if (pFramesOut != NULL) { /* Read. */ - float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; - ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels; - while (framesProcessed < frameCount) { float* pRunningFramesOut; ma_uint32 framesToRead; - ma_uint32 framesJustRead; + ma_uint32 framesJustRead = 0; framesToRead = frameCount - framesProcessed; - if (framesToRead > tempCapInFrames) { - framesToRead = tempCapInFrames; - } - pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels); if (doesOutputBufferHaveContent == MA_FALSE) { @@ -71755,11 +72502,32 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead, globalTime + framesProcessed); } else { /* Slow path. Not the first attachment. Mixing required. */ - result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead, globalTime + framesProcessed); - if (result == MA_SUCCESS || result == MA_AT_END) { - if (isSilentOutput == MA_FALSE) { /* Don't mix if the node outputs silence. */ - ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1); + ma_uint32 preMixBufferCapInFrames = ((ma_node_base*)pInputNode)->cachedDataCapInFramesPerBus; + float* pPreMixBuffer = (float*)ma_stack_alloc(((ma_node_base*)pInputNode)->pNodeGraph->pPreMixStack, preMixBufferCapInFrames * inputChannels * sizeof(float)); + + if (pPreMixBuffer == NULL) { + /* + If you're hitting this assert it means you've got an unusually deep chain of nodes, you've got an excessively large processing + size, or you have a combination of both, and as a result have run out of stack space. You can increase this using the + preMixStackSizeInBytes variable in ma_node_graph_config. If you're using ma_engine, you can do it via the preMixStackSizeInBytes + variable in ma_engine_config. It defaults to 512KB per output channel. + */ + MA_ASSERT(MA_FALSE); + } else { + if (framesToRead > preMixBufferCapInFrames) { + framesToRead = preMixBufferCapInFrames; } + + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pPreMixBuffer, framesToRead, &framesJustRead, globalTime + framesProcessed); + if (result == MA_SUCCESS || result == MA_AT_END) { + if (isSilentOutput == MA_FALSE) { /* Don't mix if the node outputs silence. */ + ma_mix_pcm_frames_f32(pRunningFramesOut, pPreMixBuffer, framesJustRead, inputChannels, /*volume*/1); + } + } + + /* The pre-mix buffer is no longer required. */ + ma_stack_free(((ma_node_base*)pInputNode)->pNodeGraph->pPreMixStack, pPreMixBuffer); + pPreMixBuffer = NULL; } } @@ -71814,6 +72582,25 @@ MA_API ma_node_config ma_node_config_init(void) return config; } +static ma_uint16 ma_node_config_get_cache_size_in_frames(const ma_node_config* pConfig, const ma_node_graph* pNodeGraph) +{ + ma_uint32 cacheSizeInFrames; + + (void)pConfig; + + if (pNodeGraph->processingSizeInFrames > 0) { + cacheSizeInFrames = pNodeGraph->processingSizeInFrames; + } else { + cacheSizeInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + } + + if (cacheSizeInFrames > 0xFFFF) { + cacheSizeInFrames = 0xFFFF; + } + + return (ma_uint16)cacheSizeInFrames; +} + static ma_result ma_node_detach_full(ma_node* pNode); @@ -71968,7 +72755,7 @@ static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_nod /* Cached audio data. - We need to allocate memory for a caching both input and output data. We have an optimization + We need to allocate memory for caching both input and output data. We have an optimization where no caching is necessary for specific conditions: - The node has 0 inputs and 1 output. @@ -71987,14 +72774,18 @@ static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_nod } else { /* Slow path. Cache needed. */ size_t cachedDataSizeInBytes = 0; + ma_uint32 cacheCapInFrames; ma_uint32 iBus; + /* The capacity of the cache is based on our callback processing size. */ + cacheCapInFrames = ma_node_config_get_cache_size_in_frames(pConfig, pNodeGraph); + for (iBus = 0; iBus < inputBusCount; iBus += 1) { - cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); + cachedDataSizeInBytes += cacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); } for (iBus = 0; iBus < outputBusCount; iBus += 1) { - cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); + cachedDataSizeInBytes += cacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); } pHeapLayout->cachedDataOffset = pHeapLayout->sizeInBytes; @@ -72080,13 +72871,12 @@ MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_n if (heapLayout.cachedDataOffset != MA_SIZE_MAX) { pNodeBase->pCachedData = (float*)ma_offset_ptr(pHeap, heapLayout.cachedDataOffset); - pNodeBase->cachedDataCapInFramesPerBus = pNodeGraph->nodeCacheCapInFrames; + pNodeBase->cachedDataCapInFramesPerBus = ma_node_config_get_cache_size_in_frames(pConfig, pNodeGraph); } else { pNodeBase->pCachedData = NULL; } - /* We need to run an initialization step for each input and output bus. */ for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { result = ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]); @@ -72260,7 +73050,7 @@ static ma_result ma_node_detach_full(ma_node* pNode) /* At this point all output buses will have been detached from the graph and we can be guaranteed - that none of it's input nodes will be getting processed by the graph. We can detach these + that none of its input nodes will be getting processed by the graph. We can detach these without needing to worry about the audio thread touching them. */ for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNode); iInputBus += 1) { @@ -72275,7 +73065,7 @@ static ma_result ma_node_detach_full(ma_node* pNode) linked list logic. We don't need to worry about the audio thread referencing these because the step above severed the connection to the graph. */ - for (pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pOutputBus->pNext)) { + for (pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)ma_atomic_load_ptr(&pInputBus->head.pNext)) { ma_node_detach_output_bus(pOutputBus->pNode, pOutputBus->outputBusIndex); /* This won't do any waiting in practice and should be efficient. */ } } @@ -72297,7 +73087,7 @@ MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIn return MA_INVALID_ARGS; /* Invalid output bus index. */ } - /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */ + /* We need to lock the output bus because we need to inspect the input node and grab its input bus. */ ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]); { pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode; @@ -72463,7 +73253,7 @@ MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_ui /* Getting here means the node is marked as started, but it may still not be truly started due to - it's start time not having been reached yet. Also, the stop time may have also been reached in + its start time not having been reached yet. Also, the stop time may have also been reached in which case it'll be considered stopped. */ if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) { @@ -72474,7 +73264,7 @@ MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_ui return ma_node_state_stopped; /* Stop time has been reached. */ } - /* Getting here means the node is marked as started and is within it's start/stop times. */ + /* Getting here means the node is marked as started and is within its start/stop times. */ return ma_node_state_started; } @@ -72636,12 +73426,12 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde frameCountOut = totalFramesRead; if (totalFramesRead > 0) { - ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Explicit cast to silence the warning. */ } /* A passthrough should never have modified the input and output frame counts. If you're - triggering these assers you need to fix your processing callback. + triggering these asserts you need to fix your processing callback. */ MA_ASSERT(frameCountIn == totalFramesRead); MA_ASSERT(frameCountOut == totalFramesRead); @@ -72819,7 +73609,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde frames available right now. */ if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { - ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Explicit cast to silence the warning. */ } else { frameCountOut = 0; /* No data was processed. */ } @@ -74056,7 +74846,7 @@ static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngin { MA_ASSERT(pEngineNode != NULL); - /* Don't try to be clever by skiping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ + /* Don't try to be clever by skipping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ return !ma_atomic_load_explicit_32(&pEngineNode->isPitchDisabled, ma_atomic_memory_order_acquire); } @@ -74093,7 +74883,7 @@ static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float vo /* If we're not smoothing we should bypass the volume gainer entirely. */ if (pEngineNode->volumeSmoothTimeInPCMFrames == 0) { - /* We should always have an active spatializer because it can be enabled and disabled dynamically. We can just use that for hodling our volume. */ + /* We should always have an active spatializer because it can be enabled and disabled dynamically. We can just use that for holding our volume. */ ma_spatializer_set_master_volume(&pEngineNode->spatializer, volume); } else { /* We're using volume smoothing, so apply the master volume to the gainer. */ @@ -74408,7 +75198,7 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float ma_sound_set_at_end(pSound, MA_TRUE); /* This will be set to false in ma_sound_start(). */ } - pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound))); + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_node_get_output_channels(pNode, 0)); frameCountIn = (ma_uint32)framesJustRead; frameCountOut = framesRemaining; @@ -74739,7 +75529,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p /* - Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to + Spatialization comes next. We spatialize based on the node's output channel count. It's up the caller to ensure channels counts link up correctly in the node graph. */ spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); @@ -74929,6 +75719,21 @@ static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOu ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL); } + +static ma_uint32 ma_device__get_processing_size_in_frames(ma_device* pDevice) +{ + /* + The processing size is the period size. The device can have a fixed sized processing size, or + it can be decided by the backend in which case it can be variable. + */ + if (pDevice->playback.intermediaryBufferCap > 0) { + /* Using a fixed sized processing callback. */ + return pDevice->playback.intermediaryBufferCap; + } else { + /* Not using a fixed sized processing callback. Need to estimate the processing size based on the backend. */ + return pDevice->playback.internalPeriodSizeInFrames; + } +} #endif MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) @@ -75022,6 +75827,14 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng if (pEngine->pDevice != NULL) { engineConfig.channels = pEngine->pDevice->playback.channels; engineConfig.sampleRate = pEngine->pDevice->sampleRate; + + /* + The processing size used by the engine is determined by engineConfig.periodSizeInFrames. We want + to make this equal to what the device is using for it's period size. If we don't do that, it's + possible that the node graph will split it's processing into multiple passes which can introduce + glitching. + */ + engineConfig.periodSizeInFrames = ma_device__get_processing_size_in_frames(pEngine->pDevice); } } #endif @@ -75048,9 +75861,10 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng } - /* The engine is a node graph. This needs to be initialized after we have the device so we can can determine the channel count. */ + /* The engine is a node graph. This needs to be initialized after we have the device so we can determine the channel count. */ nodeGraphConfig = ma_node_graph_config_init(engineConfig.channels); - nodeGraphConfig.nodeCacheCapInFrames = (engineConfig.periodSizeInFrames > 0xFFFF) ? 0xFFFF : (ma_uint16)engineConfig.periodSizeInFrames; + nodeGraphConfig.processingSizeInFrames = engineConfig.periodSizeInFrames; + nodeGraphConfig.preMixStackSizeInBytes = engineConfig.preMixStackSizeInBytes; result = ma_node_graph_init(&nodeGraphConfig, &pEngine->allocationCallbacks, &pEngine->nodeGraph); if (result != MA_SUCCESS) { @@ -75130,8 +75944,8 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; - /* The Emscripten build cannot use threads. */ - #if defined(MA_EMSCRIPTEN) + /* The Emscripten build cannot use threads unless it's targeting pthreads. */ + #if defined(MA_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) { resourceManagerConfig.jobThreadCount = 0; resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; @@ -75646,7 +76460,7 @@ MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePa return MA_INVALID_ARGS; } - /* Attach to the endpoint node if nothing is specicied. */ + /* Attach to the endpoint node if nothing is specified. */ if (pNode == NULL) { pNode = ma_node_graph_get_endpoint(&pEngine->nodeGraph); nodeInputBusIndex = 0; @@ -75863,7 +76677,7 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); } - ma_sound_set_looping(pSound, pConfig->isLooping); + ma_sound_set_looping(pSound, pConfig->isLooping || ((pConfig->flags & MA_SOUND_FLAG_LOOPING) != 0)); return MA_SUCCESS; } @@ -75887,6 +76701,9 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s it and can avoid accessing the sound from within the notification. */ flags = pConfig->flags | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT; + if (pConfig->isLooping) { + flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); if (pSound->pResourceManagerDataSource == NULL) { @@ -75915,7 +76732,7 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s resourceManagerDataSourceConfig.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; resourceManagerDataSourceConfig.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; resourceManagerDataSourceConfig.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; - resourceManagerDataSourceConfig.isLooping = pConfig->isLooping; + resourceManagerDataSourceConfig.isLooping = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0; result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); if (result != MA_SUCCESS) { @@ -76067,7 +76884,7 @@ MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pCo { /* Getting here means we're not loading from a file. We may be loading from an already-initialized - data source, or none at all. If we aren't specifying any data source, we'll be initializing the + data source, or none at all. If we aren't specifying any data source, we'll be initializing the equivalent to a group. ma_data_source_init_from_data_source_internal() will deal with this for us, so no special treatment required here. */ @@ -76787,6 +77604,27 @@ MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameInd return MA_SUCCESS; } +MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds) +{ + ma_uint64 frameIndex; + ma_uint32 sampleRate; + ma_result result; + + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_sound_get_data_format(pSound, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + /* We need PCM frames. We need to convert first */ + frameIndex = (ma_uint64)(seekPointInSeconds * sampleRate); + + return ma_sound_seek_to_pcm_frame(pSound, frameIndex); +} + MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { if (pSound == NULL) { @@ -77233,7 +78071,7 @@ code below please report the bug to the respective repository for the relevant p *************************************************************************************************************************************************************** **************************************************************************************************************************************************************/ #if !defined(MA_NO_WAV) && (!defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING)) -#if !defined(MA_DR_WAV_IMPLEMENTATION) && !defined(MA_DR_WAV_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ +#if !defined(MA_DR_WAV_IMPLEMENTATION) /* dr_wav_c begin */ #ifndef ma_dr_wav_c #define ma_dr_wav_c @@ -78555,7 +79393,6 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p } if (pWav->container == ma_dr_wav_container_riff || pWav->container == ma_dr_wav_container_rifx) { if (ma_dr_wav_bytes_to_u32_ex(chunkSizeBytes, pWav->container) < 36) { - return MA_FALSE; } } else if (pWav->container == ma_dr_wav_container_rf64) { if (ma_dr_wav_bytes_to_u32_le(chunkSizeBytes) != 0xFFFFFFFF) { @@ -78824,7 +79661,9 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p compressionFormat = MA_DR_WAVE_FORMAT_MULAW; } else if (ma_dr_wav_fourcc_equal(type, "ima4")) { compressionFormat = MA_DR_WAVE_FORMAT_DVI_ADPCM; - sampleSizeInBits = 4; + sampleSizeInBits = 4; + (void)compressionFormat; + (void)sampleSizeInBits; return MA_FALSE; } else { return MA_FALSE; @@ -78882,9 +79721,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p } } if (isProcessingMetadata) { - ma_uint64 metadataBytesRead; - metadataBytesRead = ma_dr_wav__metadata_process_chunk(&metadataParser, &header, ma_dr_wav_metadata_type_all_including_unknown); - MA_DR_WAV_ASSERT(metadataBytesRead <= header.sizeInBytes); + ma_dr_wav__metadata_process_chunk(&metadataParser, &header, ma_dr_wav_metadata_type_all_including_unknown); if (ma_dr_wav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData) == MA_FALSE) { break; } @@ -80332,6 +81169,12 @@ MA_API ma_uint64 ma_dr_wav_write_pcm_frames(ma_dr_wav* pWav, ma_uint64 framesToW MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_uint64 framesToRead, ma_int16* pBufferOut) { ma_uint64 totalFramesRead = 0; + static ma_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static ma_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static ma_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; MA_DR_WAV_ASSERT(pWav != NULL); MA_DR_WAV_ASSERT(framesToRead > 0); while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { @@ -80350,6 +81193,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table)) { + return totalFramesRead; + } } else { ma_uint8 header[14]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { @@ -80369,6 +81215,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.cachedFrameCount = 2; + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff2Table)) { + return totalFramesRead; + } } } while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { @@ -80391,12 +81240,6 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ if (pWav->msadpcm.bytesRemainingInBlock == 0) { continue; } else { - static ma_int32 adaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 - }; - static ma_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; - static ma_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; ma_uint8 nibbles; ma_int32 nibble0; ma_int32 nibble1; @@ -81647,7 +82490,7 @@ MA_API void ma_dr_wav_f32_to_s32(ma_int32* pOut, const float* pIn, size_t sample return; } for (i = 0; i < sampleCount; ++i) { - *pOut++ = (ma_int32)(2147483648.0 * pIn[i]); + *pOut++ = (ma_int32)(2147483648.0f * pIn[i]); } } MA_API void ma_dr_wav_f64_to_s32(ma_int32* pOut, const double* pIn, size_t sampleCount) @@ -82061,7 +82904,7 @@ MA_API ma_bool32 ma_dr_wav_fourcc_equal(const ma_uint8* a, const char* b) #endif /* MA_NO_WAV */ #if !defined(MA_NO_FLAC) && !defined(MA_NO_DECODING) -#if !defined(MA_DR_FLAC_IMPLEMENTATION) && !defined(MA_DR_FLAC_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ +#if !defined(MA_DR_FLAC_IMPLEMENTATION) /* dr_flac_c begin */ #ifndef ma_dr_flac_c #define ma_dr_flac_c @@ -85093,6 +85936,7 @@ static ma_bool32 ma_dr_flac__read_subframe_header(ma_dr_flac_bs* bs, ma_dr_flac_ if ((header & 0x80) != 0) { return MA_FALSE; } + pSubframe->lpcOrder = 0; type = (header & 0x7E) >> 1; if (type == 0) { pSubframe->subframeType = MA_DR_FLAC_SUBFRAME_CONSTANT; @@ -85150,6 +85994,9 @@ static ma_bool32 ma_dr_flac__decode_subframe(ma_dr_flac_bs* bs, ma_dr_flac_frame } subframeBitsPerSample -= pSubframe->wastedBitsPerSample; pSubframe->pSamplesS32 = pDecodedSamplesOut; + if (frame->header.blockSizeInPCMFrames < pSubframe->lpcOrder) { + return MA_FALSE; + } switch (pSubframe->subframeType) { case MA_DR_FLAC_SUBFRAME_CONSTANT: @@ -89806,7 +90653,7 @@ MA_API ma_bool32 ma_dr_flac_next_cuesheet_track(ma_dr_flac_cuesheet_track_iterat #endif /* MA_NO_FLAC */ #if !defined(MA_NO_MP3) && !defined(MA_NO_DECODING) -#if !defined(MA_DR_MP3_IMPLEMENTATION) && !defined(MA_DR_MP3_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ +#if !defined(MA_DR_MP3_IMPLEMENTATION) /* dr_mp3_c begin */ #ifndef ma_dr_mp3_c #define ma_dr_mp3_c @@ -89867,7 +90714,7 @@ MA_API const char* ma_dr_mp3_version_string(void) #define MA_DR_MP3_MIN(a, b) ((a) > (b) ? (b) : (a)) #define MA_DR_MP3_MAX(a, b) ((a) < (b) ? (b) : (a)) #if !defined(MA_DR_MP3_NO_SIMD) -#if !defined(MA_DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) +#if !defined(MA_DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) #define MA_DR_MP3_ONLY_SIMD #endif #if ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) @@ -89940,7 +90787,7 @@ end: return g_have_simd - 1; #endif } -#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #include #define MA_DR_MP3_HAVE_SSE 0 #define MA_DR_MP3_HAVE_SIMD 1 @@ -89969,7 +90816,7 @@ static int ma_dr_mp3_have_simd(void) #else #define MA_DR_MP3_HAVE_SIMD 0 #endif -#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__ARM_ARCH_6M__) +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(_M_ARM64EC) && !defined(__ARM_ARCH_6M__) #define MA_DR_MP3_HAVE_ARMV6 1 static __inline__ __attribute__((always_inline)) ma_int32 ma_dr_mp3_clip_int16_arm(ma_int32 a) { @@ -91135,8 +91982,8 @@ static ma_int16 ma_dr_mp3d_scale_pcm(float sample) s32 -= (s32 < 0); s = (ma_int16)ma_dr_mp3_clip_int16_arm(s32); #else - if (sample >= 32766.5) return (ma_int16) 32767; - if (sample <= -32767.5) return (ma_int16)-32768; + if (sample >= 32766.5f) return (ma_int16) 32767; + if (sample <= -32767.5f) return (ma_int16)-32768; s = (ma_int16)(sample + .5f); s -= (s < 0); #endif @@ -91522,9 +92369,9 @@ MA_API void ma_dr_mp3dec_f32_to_s16(const float *in, ma_int16 *out, size_t num_s for(; i < num_samples; i++) { float sample = in[i] * 32768.0f; - if (sample >= 32766.5) + if (sample >= 32766.5f) out[i] = (ma_int16) 32767; - else if (sample <= -32767.5) + else if (sample <= -32767.5f) out[i] = (ma_int16)-32768; else { @@ -92602,7 +93449,7 @@ For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== -Copyright 2023 David Reid +Copyright 2025 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/vendor/miniaudio/synchronization.odin b/vendor/miniaudio/synchronization.odin index 012f52c2c..08182c32b 100644 --- a/vendor/miniaudio/synchronization.odin +++ b/vendor/miniaudio/synchronization.odin @@ -62,6 +62,11 @@ when !NO_THREADING { Signals the specified auto-reset event. */ event_signal :: proc(pEvent: ^event) -> result --- + + semaphore_init :: proc(initialValue: i32, pSemaphore: ^semaphore) -> result --- + semaphore_uninit :: proc(pSemaphore: ^semaphore) --- + semaphore_wait :: proc(pSemaphore: ^semaphore) -> result --- + semaphore_release :: proc(pSemaphore: ^semaphore) -> result --- } /* NO_THREADING */ } diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 9285874b6..d59bb75a4 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -7,7 +7,7 @@ foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { /* - Calculates a buffer size in milliseconds from the specified number of frames and sample rate. + Calculates a buffer size in milliseconds (rounded up) from the specified number of frames and sample rate. */ calculate_buffer_size_in_milliseconds_from_frames :: proc(bufferSizeInFrames: u32, sampleRate: u32) -> u32 --- @@ -163,6 +163,8 @@ foreign lib { data_source_read_pcm_frames :: proc(pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ data_source_seek_pcm_frames :: proc(pDataSource: ^data_source, frameCount: u64, pFramesSeeked: ^u64) -> result --- /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ data_source_seek_to_pcm_frame :: proc(pDataSource: ^data_source, frameIndex: u64) -> result --- + data_source_seek_seconds :: proc(pDataSource: ^data_source, secondCount: f32, pSecondsSeeked: ^f32) -> result --- /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */ + data_source_seek_to_seconds :: proc(pDataSource: ^data_source, seekPointInSeconds: f32) -> result --- /* Abstraction to ma_data_source_seek_to_pcm_frame() */ data_source_get_data_format :: proc(pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^data_source, pCursor: ^u64) -> result --- data_source_get_length_in_pcm_frames :: proc(pDataSource: ^data_source, pLength: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ diff --git a/vendor/raylib/rlgl/rlgl.odin b/vendor/raylib/rlgl/rlgl.odin index 6ac19695d..b6cc23c48 100644 --- a/vendor/raylib/rlgl/rlgl.odin +++ b/vendor/raylib/rlgl/rlgl.odin @@ -375,17 +375,20 @@ foreign lib { //------------------------------------------------------------------------------------ // Functions Declaration - Matrix operations //------------------------------------------------------------------------------------ - MatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed - PushMatrix :: proc() --- // Push the current matrix to stack - PopMatrix :: proc() --- // Pop lattest inserted matrix from stack - LoadIdentity :: proc() --- // Reset current matrix to identity matrix - Translatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix - Rotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix - Scalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix - MultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix - Frustum :: proc(left, right, bottom, top, znear, zfar: f64) --- - Ortho :: proc(left, right, bottom, top, znear, zfar: f64) --- - Viewport :: proc(x, y, width, height: c.int) --- // Set the viewport area + MatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed + PushMatrix :: proc() --- // Push the current matrix to stack + PopMatrix :: proc() --- // Pop lattest inserted matrix from stack + LoadIdentity :: proc() --- // Reset current matrix to identity matrix + Translatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix + Rotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix + Scalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix + MultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix + Frustum :: proc(left, right, bottom, top, znear, zfar: f64) --- + Ortho :: proc(left, right, bottom, top, znear, zfar: f64) --- + Viewport :: proc(x, y, width, height: c.int) --- // Set the viewport area + SetClipPlanes :: proc(near, far: f64) --- // Set clip planes distances + GetCullDistanceNear :: proc() -> f64 --- // Get cull plane distance near + GetCullDistanceFar :: proc() -> f64 --- // Get cull plane distance far //------------------------------------------------------------------------------------ // Functions Declaration - Vertex level operations diff --git a/vendor/sdl3/sdl3_gpu.odin b/vendor/sdl3/sdl3_gpu.odin index ec414f98e..f0017a525 100644 --- a/vendor/sdl3/sdl3_gpu.odin +++ b/vendor/sdl3/sdl3_gpu.odin @@ -46,7 +46,7 @@ GPUIndexElementSize :: enum c.int { GPUTextureFormat :: enum c.int { INVALID, - /* Unsigned Normalized Float Color Formats */ + /* Unsigned Normalized Float Color Formats */ A8_UNORM, R8_UNORM, R8G8_UNORM, @@ -59,34 +59,41 @@ GPUTextureFormat :: enum c.int { B5G5R5A1_UNORM, B4G4R4A4_UNORM, B8G8R8A8_UNORM, - /* Compressed Unsigned Normalized Float Color Formats */ + + /* Compressed Unsigned Normalized Float Color Formats */ BC1_RGBA_UNORM, BC2_RGBA_UNORM, BC3_RGBA_UNORM, BC4_R_UNORM, BC5_RG_UNORM, BC7_RGBA_UNORM, - /* Compressed Signed Float Color Formats */ + + /* Compressed Signed Float Color Formats */ BC6H_RGB_FLOAT, - /* Compressed Unsigned Float Color Formats */ + + /* Compressed Unsigned Float Color Formats */ BC6H_RGB_UFLOAT, - /* Signed Normalized Float Color Formats */ + + /* Signed Normalized Float Color Formats */ R8_SNORM, R8G8_SNORM, R8G8B8A8_SNORM, R16_SNORM, R16G16_SNORM, R16G16B16A16_SNORM, - /* Signed Float Color Formats */ + + /* Signed Float Color Formats */ R16_FLOAT, R16G16_FLOAT, R16G16B16A16_FLOAT, R32_FLOAT, R32G32_FLOAT, R32G32B32A32_FLOAT, - /* Unsigned Float Color Formats */ + + /* Unsigned Float Color Formats */ R11G11B10_UFLOAT, - /* Unsigned Integer Color Formats */ + + /* Unsigned Integer Color Formats */ R8_UINT, R8G8_UINT, R8G8B8A8_UINT, @@ -96,7 +103,8 @@ GPUTextureFormat :: enum c.int { R32_UINT, R32G32_UINT, R32G32B32A32_UINT, - /* Signed Integer Color Formats */ + + /* Signed Integer Color Formats */ R8_INT, R8G8_INT, R8G8B8A8_INT, @@ -106,21 +114,25 @@ GPUTextureFormat :: enum c.int { R32_INT, R32G32_INT, R32G32B32A32_INT, - /* SRGB Unsigned Normalized Color Formats */ + + /* SRGB Unsigned Normalized Color Formats */ R8G8B8A8_UNORM_SRGB, B8G8R8A8_UNORM_SRGB, - /* Compressed SRGB Unsigned Normalized Color Formats */ + + /* Compressed SRGB Unsigned Normalized Color Formats */ BC1_RGBA_UNORM_SRGB, BC2_RGBA_UNORM_SRGB, BC3_RGBA_UNORM_SRGB, BC7_RGBA_UNORM_SRGB, - /* Depth Formats */ + + /* Depth Formats */ D16_UNORM, D24_UNORM, D32_FLOAT, D24_UNORM_S8_UINT, D32_FLOAT_S8_UINT, - /* Compressed ASTC Normalized Float Color Formats*/ + + /* Compressed ASTC Normalized Float Color Formats*/ ASTC_4x4_UNORM, ASTC_5x4_UNORM, ASTC_5x5_UNORM, @@ -135,7 +147,8 @@ GPUTextureFormat :: enum c.int { ASTC_10x10_UNORM, ASTC_12x10_UNORM, ASTC_12x12_UNORM, - /* Compressed SRGB ASTC Normalized Float Color Formats*/ + + /* Compressed SRGB ASTC Normalized Float Color Formats*/ ASTC_4x4_UNORM_SRGB, ASTC_5x4_UNORM_SRGB, ASTC_5x5_UNORM_SRGB, @@ -150,7 +163,8 @@ GPUTextureFormat :: enum c.int { ASTC_10x10_UNORM_SRGB, ASTC_12x10_UNORM_SRGB, ASTC_12x12_UNORM_SRGB, - /* Compressed ASTC Signed Float Color Formats*/ + + /* Compressed ASTC Signed Float Color Formats*/ ASTC_4x4_FLOAT, ASTC_5x4_FLOAT, ASTC_5x5_FLOAT, diff --git a/vendor/sdl3/sdl3_mutex.odin b/vendor/sdl3/sdl3_mutex.odin index ada8006bc..8067473f3 100644 --- a/vendor/sdl3/sdl3_mutex.odin +++ b/vendor/sdl3/sdl3_mutex.odin @@ -1,8 +1,8 @@ package sdl3 -Mutex :: struct {} -RWLock :: struct {} - +Mutex :: struct {} +RWLock :: struct {} +Semaphore :: struct {} @(default_calling_convention="c", link_prefix="SDL_", require_results) foreign lib { @@ -19,4 +19,12 @@ foreign lib { TryLockRWLockForWriting :: proc(rwlock: ^RWLock) -> bool --- UnlockRWLock :: proc(rwlock: ^RWLock) --- DestroyRWLock :: proc(rwlock: ^RWLock) --- -} \ No newline at end of file + + CreateSemaphore :: proc(initial_value: Uint32) -> ^Semaphore --- + DestroySemaphore :: proc(sem: ^Semaphore) --- + GetSemaphoreValue :: proc(sem: ^Semaphore) -> Uint32 --- + SignalSemaphore :: proc(sem: ^Semaphore) --- + TryWaitSemaphore :: proc(sem: ^Semaphore) -> bool --- + WaitSemaphore :: proc(sem: ^Semaphore) --- + WaitSemaphoreTimeout :: proc(sem: ^Semaphore, timeout_ms: Sint32) --- +} diff --git a/vendor/sdl3/sdl3_thread.odin b/vendor/sdl3/sdl3_thread.odin index 9b2d068d9..e6bbf7d16 100644 --- a/vendor/sdl3/sdl3_thread.odin +++ b/vendor/sdl3/sdl3_thread.odin @@ -49,7 +49,7 @@ PROP_THREAD_CREATE_STACKSIZE_NUMBER :: "SDL.thread.create.stacksize" BeginThreadFunction :: proc "c" () -> FunctionPointer { when ODIN_OS == .Windows { foreign { - _beginthreadx :: proc "c" ( + _beginthreadex :: proc "c" ( security: rawptr, stack_size: c.uint, start_address: proc "c" (rawptr), @@ -58,7 +58,7 @@ BeginThreadFunction :: proc "c" () -> FunctionPointer { thraddr: ^c.uint, ) -> uintptr --- } - return FunctionPointer(_beginthreadx) + return FunctionPointer(_beginthreadex) } else { return nil } diff --git a/vendor/sdl3/ttf/LICENSE.freetype.txt b/vendor/sdl3/ttf/LICENSE.freetype.txt new file mode 100644 index 000000000..c406d150f --- /dev/null +++ b/vendor/sdl3/ttf/LICENSE.freetype.txt @@ -0,0 +1,169 @@ + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + https://www.freetype.org + + +--- end of FTL.TXT --- diff --git a/vendor/sdl3/ttf/LICENSE.harfbuzz.txt b/vendor/sdl3/ttf/LICENSE.harfbuzz.txt new file mode 100644 index 000000000..1dd917e9f --- /dev/null +++ b/vendor/sdl3/ttf/LICENSE.harfbuzz.txt @@ -0,0 +1,42 @@ +HarfBuzz is licensed under the so-called "Old MIT" license. Details follow. +For parts of HarfBuzz that are licensed under different licenses see individual +files names COPYING in subdirectories where applicable. + +Copyright © 2010-2022 Google, Inc. +Copyright © 2015-2020 Ebrahim Byagowi +Copyright © 2019,2020 Facebook, Inc. +Copyright © 2012,2015 Mozilla Foundation +Copyright © 2011 Codethink Limited +Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies) +Copyright © 2009 Keith Stribley +Copyright © 2011 Martin Hosken and SIL International +Copyright © 2007 Chris Wilson +Copyright © 2005,2006,2020,2021,2022,2023 Behdad Esfahbod +Copyright © 2004,2007,2008,2009,2010,2013,2021,2022,2023 Red Hat, Inc. +Copyright © 1998-2005 David Turner and Werner Lemberg +Copyright © 2016 Igalia S.L. +Copyright © 2022 Matthias Clasen +Copyright © 2018,2021 Khaled Hosny +Copyright © 2018,2019,2020 Adobe, Inc +Copyright © 2013-2015 Alexei Podtelezhnikov + +For full copyright notices consult the individual files in the package. + + +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the +above copyright notice and the following two paragraphs appear in +all copies of this software. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/vendor/sdl3/ttf/LICENSE.plutosvg.txt b/vendor/sdl3/ttf/LICENSE.plutosvg.txt new file mode 100644 index 000000000..62a964e73 --- /dev/null +++ b/vendor/sdl3/ttf/LICENSE.plutosvg.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2025 Samuel Ugochukwu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/sdl3/ttf/LICENSE.plutovg.txt b/vendor/sdl3/ttf/LICENSE.plutovg.txt new file mode 100644 index 000000000..62a964e73 --- /dev/null +++ b/vendor/sdl3/ttf/LICENSE.plutovg.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2025 Samuel Ugochukwu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/sdl3/ttf/LICENSE.txt b/vendor/sdl3/ttf/LICENSE.txt new file mode 100644 index 000000000..52d0ed38b --- /dev/null +++ b/vendor/sdl3/ttf/LICENSE.txt @@ -0,0 +1,17 @@ +Copyright (C) 1997-2025 Sam Lantinga + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/vendor/sdl3/ttf/SDL3_ttf.dll b/vendor/sdl3/ttf/SDL3_ttf.dll new file mode 100644 index 000000000..b1616f966 Binary files /dev/null and b/vendor/sdl3/ttf/SDL3_ttf.dll differ diff --git a/vendor/sdl3/ttf/SDL3_ttf.lib b/vendor/sdl3/ttf/SDL3_ttf.lib new file mode 100644 index 000000000..5716c5661 Binary files /dev/null and b/vendor/sdl3/ttf/SDL3_ttf.lib differ diff --git a/vendor/sdl3/ttf/include/SDL_textengine.h b/vendor/sdl3/ttf/include/SDL_textengine.h new file mode 100644 index 000000000..9f5f1f0c3 --- /dev/null +++ b/vendor/sdl3/ttf/include/SDL_textengine.h @@ -0,0 +1,181 @@ +/* + SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts + Copyright (C) 2001-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + + +/** + * \file SDL_textengine.h + * + * Definitions for implementations of the TTF_TextEngine interface. + */ +#ifndef SDL_TTF_TEXTENGINE_H_ +#define SDL_TTF_TEXTENGINE_H_ + +#include +#include + +#include + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A font atlas draw command. + * + * \since This enum is available since SDL_ttf 3.0.0. + */ +typedef enum TTF_DrawCommand +{ + TTF_DRAW_COMMAND_NOOP, + TTF_DRAW_COMMAND_FILL, + TTF_DRAW_COMMAND_COPY +} TTF_DrawCommand; + +/** + * A filled rectangle draw operation. + * + * \since This struct is available since SDL_ttf 3.0.0. + * + * \sa TTF_DrawOperation + */ +typedef struct TTF_FillOperation +{ + TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */ + SDL_Rect rect; /**< The rectangle to fill, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */ +} TTF_FillOperation; + +/** + * A texture copy draw operation. + * + * \since This struct is available since SDL_ttf 3.0.0. + * + * \sa TTF_DrawOperation + */ +typedef struct TTF_CopyOperation +{ + TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */ + int text_offset; /**< The offset in the text corresponding to this glyph. + There may be multiple glyphs with the same text offset + and the next text offset might be several Unicode codepoints + later. In this case the glyphs and codepoints are grouped + together and the group bounding box is the union of the dst + rectangles for the corresponding glyphs. */ + TTF_Font *glyph_font; /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */ + Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */ + SDL_Rect src; /**< The area within the glyph to be drawn */ + SDL_Rect dst; /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */ + void *reserved; +} TTF_CopyOperation; + +/** + * A text engine draw operation. + * + * \since This struct is available since SDL_ttf 3.0.0. + */ +typedef union TTF_DrawOperation +{ + TTF_DrawCommand cmd; + TTF_FillOperation fill; + TTF_CopyOperation copy; +} TTF_DrawOperation; + + +/* Private data in TTF_Text, to assist in text measurement and layout */ +typedef struct TTF_TextLayout TTF_TextLayout; + + +/* Private data in TTF_Text, available to implementations */ +struct TTF_TextData +{ + TTF_Font *font; /**< The font used by this text, read-only. */ + SDL_FColor color; /**< The color of the text, read-only. */ + + bool needs_layout_update; /**< True if the layout needs to be updated */ + TTF_TextLayout *layout; /**< Cached layout information, read-only. */ + int x; /**< The x offset of the upper left corner of this text, in pixels, read-only. */ + int y; /**< The y offset of the upper left corner of this text, in pixels, read-only. */ + int w; /**< The width of this text, in pixels, read-only. */ + int h; /**< The height of this text, in pixels, read-only. */ + int num_ops; /**< The number of drawing operations to render this text, read-only. */ + TTF_DrawOperation *ops; /**< The drawing operations used to render this text, read-only. */ + int num_clusters; /**< The number of substrings representing clusters of glyphs in the string, read-only */ + TTF_SubString *clusters; /**< Substrings representing clusters of glyphs in the string, read-only */ + + SDL_PropertiesID props; /**< Custom properties associated with this text, read-only. This field is created as-needed using TTF_GetTextProperties() and the properties may be then set and read normally */ + + bool needs_engine_update; /**< True if the engine text needs to be updated */ + TTF_TextEngine *engine; /**< The engine used to render this text, read-only. */ + void *engine_text; /**< The implementation-specific representation of this text */ +}; + +/** + * A text engine interface. + * + * This structure should be initialized using SDL_INIT_INTERFACE() + * + * \since This struct is available since SDL_ttf 3.0.0. + * + * \sa SDL_INIT_INTERFACE + */ +struct TTF_TextEngine +{ + Uint32 version; /**< The version of this interface */ + + void *userdata; /**< User data pointer passed to callbacks */ + + /* Create a text representation from draw instructions. + * + * All fields of `text` except `internal->engine_text` will already be filled out. + * + * This function should set the `internal->engine_text` field to a non-NULL value. + * + * \param userdata the userdata pointer in this interface. + * \param text the text object being created. + */ + bool (SDLCALL *CreateText)(void *userdata, TTF_Text *text); + + /** + * Destroy a text representation. + */ + void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text); + +}; + +/* Check the size of TTF_TextEngine + * + * If this assert fails, either the compiler is padding to an unexpected size, + * or the interface has been updated and this should be updated to match and + * the code using this interface should be updated to handle the old version. + */ +SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE, + (sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) || + (sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32)); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_TTF_TEXTENGINE_H_ */ + diff --git a/vendor/sdl3/ttf/include/SDL_ttf.h b/vendor/sdl3/ttf/include/SDL_ttf.h new file mode 100644 index 000000000..b723bc74e --- /dev/null +++ b/vendor/sdl3/ttf/include/SDL_ttf.h @@ -0,0 +1,2833 @@ +/* + SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts + Copyright (C) 2001-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: SDLTTF */ + +/** + * # CategorySDLTTF + * + * Header file for SDL_ttf library + * + * This library is a wrapper around the excellent FreeType 2.0 library, + * available at: https://www.freetype.org/ + */ + +#ifndef SDL_TTF_H_ +#define SDL_TTF_H_ + +#include +#include + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Printable format: "%d.%d.%d", MAJOR, MINOR, MICRO + */ +#define SDL_TTF_MAJOR_VERSION 3 +#define SDL_TTF_MINOR_VERSION 2 +#define SDL_TTF_MICRO_VERSION 2 + +/** + * This is the version number macro for the current SDL_ttf version. + */ +#define SDL_TTF_VERSION \ + SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, SDL_TTF_MICRO_VERSION) + +/** + * This macro will evaluate to true if compiled with SDL_ttf at least X.Y.Z. + */ +#define SDL_TTF_VERSION_ATLEAST(X, Y, Z) \ + ((SDL_TTF_MAJOR_VERSION >= X) && \ + (SDL_TTF_MAJOR_VERSION > X || SDL_TTF_MINOR_VERSION >= Y) && \ + (SDL_TTF_MAJOR_VERSION > X || SDL_TTF_MINOR_VERSION > Y || SDL_TTF_MICRO_VERSION >= Z)) + +/** + * This function gets the version of the dynamically linked SDL_ttf library. + * + * \returns SDL_ttf version. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL TTF_Version(void); + +/** + * Query the version of the FreeType library in use. + * + * TTF_Init() should be called before calling this function. + * + * \param major to be filled in with the major version number. Can be NULL. + * \param minor to be filled in with the minor version number. Can be NULL. + * \param patch to be filled in with the param version number. Can be NULL. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_Init + */ +extern SDL_DECLSPEC void SDLCALL TTF_GetFreeTypeVersion(int *major, int *minor, int *patch); + +/** + * Query the version of the HarfBuzz library in use. + * + * If HarfBuzz is not available, the version reported is 0.0.0. + * + * \param major to be filled in with the major version number. Can be NULL. + * \param minor to be filled in with the minor version number. Can be NULL. + * \param patch to be filled in with the param version number. Can be NULL. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC void SDLCALL TTF_GetHarfBuzzVersion(int *major, int *minor, int *patch); + +/** + * The internal structure containing font information. + * + * Opaque data! + */ +typedef struct TTF_Font TTF_Font; + +/** + * Initialize SDL_ttf. + * + * You must successfully call this function before it is safe to call any + * other function in this library. + * + * It is safe to call this more than once, and each successful TTF_Init() call + * should be paired with a matching TTF_Quit() call. + * + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_Quit + */ +extern SDL_DECLSPEC bool SDLCALL TTF_Init(void); + +/** + * Create a font from a file, using a specified point size. + * + * Some .fon fonts will have several sizes embedded in the file, so the point + * size becomes the index of choosing which size. If the value is too high, + * the last indexed size will be the default. + * + * When done with the returned TTF_Font, use TTF_CloseFont() to dispose of it. + * + * \param file path to font file. + * \param ptsize point size to use for the newly-opened font. + * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CloseFont + */ +extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFont(const char *file, float ptsize); + +/** + * Create a font from an SDL_IOStream, using a specified point size. + * + * Some .fon fonts will have several sizes embedded in the file, so the point + * size becomes the index of choosing which size. If the value is too high, + * the last indexed size will be the default. + * + * If `closeio` is true, `src` will be automatically closed once the font is + * closed. Otherwise you should close `src` yourself after closing the font. + * + * When done with the returned TTF_Font, use TTF_CloseFont() to dispose of it. + * + * \param src an SDL_IOStream to provide a font file's data. + * \param closeio true to close `src` when the font is closed, false to leave + * it open. + * \param ptsize point size to use for the newly-opened font. + * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CloseFont + */ +extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontIO(SDL_IOStream *src, bool closeio, float ptsize); + +/** + * Create a font with the specified properties. + * + * These are the supported properties: + * + * - `TTF_PROP_FONT_CREATE_FILENAME_STRING`: the font file to open, if an + * SDL_IOStream isn't being used. This is required if + * `TTF_PROP_FONT_CREATE_IOSTREAM_POINTER` and + * `TTF_PROP_FONT_CREATE_EXISTING_FONT` aren't set. + * - `TTF_PROP_FONT_CREATE_IOSTREAM_POINTER`: an SDL_IOStream containing the + * font to be opened. This should not be closed until the font is closed. + * This is required if `TTF_PROP_FONT_CREATE_FILENAME_STRING` and + * `TTF_PROP_FONT_CREATE_EXISTING_FONT` aren't set. + * - `TTF_PROP_FONT_CREATE_IOSTREAM_OFFSET_NUMBER`: the offset in the iostream + * for the beginning of the font, defaults to 0. + * - `TTF_PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN`: true if closing the + * font should also close the associated SDL_IOStream. + * - `TTF_PROP_FONT_CREATE_SIZE_FLOAT`: the point size of the font. Some .fon + * fonts will have several sizes embedded in the file, so the point size + * becomes the index of choosing which size. If the value is too high, the + * last indexed size will be the default. + * - `TTF_PROP_FONT_CREATE_FACE_NUMBER`: the face index of the font, if the + * font contains multiple font faces. + * - `TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER`: the horizontal DPI to use + * for font rendering, defaults to + * `TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER` if set, or 72 otherwise. + * - `TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER`: the vertical DPI to use for + * font rendering, defaults to `TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER` + * if set, or 72 otherwise. + * - `TTF_PROP_FONT_CREATE_EXISTING_FONT`: an optional TTF_Font that, if set, + * will be used as the font data source and the initial size and style of + * the new font. + * + * \param props the properties to use. + * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CloseFont + */ +extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontWithProperties(SDL_PropertiesID props); + +#define TTF_PROP_FONT_CREATE_FILENAME_STRING "SDL_ttf.font.create.filename" +#define TTF_PROP_FONT_CREATE_IOSTREAM_POINTER "SDL_ttf.font.create.iostream" +#define TTF_PROP_FONT_CREATE_IOSTREAM_OFFSET_NUMBER "SDL_ttf.font.create.iostream.offset" +#define TTF_PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN "SDL_ttf.font.create.iostream.autoclose" +#define TTF_PROP_FONT_CREATE_SIZE_FLOAT "SDL_ttf.font.create.size" +#define TTF_PROP_FONT_CREATE_FACE_NUMBER "SDL_ttf.font.create.face" +#define TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER "SDL_ttf.font.create.hdpi" +#define TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER "SDL_ttf.font.create.vdpi" +#define TTF_PROP_FONT_CREATE_EXISTING_FONT "SDL_ttf.font.create.existing_font" + +/** + * Create a copy of an existing font. + * + * The copy will be distinct from the original, but will share the font file + * and have the same size and style as the original. + * + * When done with the returned TTF_Font, use TTF_CloseFont() to dispose of it. + * + * \param existing_font the font to copy. + * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * original font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CloseFont + */ +extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_CopyFont(TTF_Font *existing_font); + +/** + * Get the properties associated with a font. + * + * The following read-write properties are provided by SDL: + * + * - `TTF_PROP_FONT_OUTLINE_LINE_CAP_NUMBER`: The FT_Stroker_LineCap value + * used when setting the font outline, defaults to + * `FT_STROKER_LINECAP_ROUND`. + * - `TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER`: The FT_Stroker_LineJoin value + * used when setting the font outline, defaults to + * `FT_STROKER_LINEJOIN_ROUND`. + * - `TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER`: The FT_Fixed miter limit used + * when setting the font outline, defaults to 0. + * + * \param font the font to query. + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL TTF_GetFontProperties(TTF_Font *font); + +#define TTF_PROP_FONT_OUTLINE_LINE_CAP_NUMBER "SDL_ttf.font.outline.line_cap" +#define TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER "SDL_ttf.font.outline.line_join" +#define TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER "SDL_ttf.font.outline.miter_limit" + +/** + * Get the font generation. + * + * The generation is incremented each time font properties change that require + * rebuilding glyphs, such as style, size, etc. + * + * \param font the font to query. + * \returns the font generation or 0 on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetFontGeneration(TTF_Font *font); + +/** + * Add a fallback font. + * + * Add a font that will be used for glyphs that are not in the current font. + * The fallback font should have the same size and style as the current font. + * + * If there are multiple fallback fonts, they are used in the order added. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to modify. + * \param fallback the font to add as a fallback. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created + * both fonts. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_ClearFallbackFonts + * \sa TTF_RemoveFallbackFont + */ +extern SDL_DECLSPEC bool SDLCALL TTF_AddFallbackFont(TTF_Font *font, TTF_Font *fallback); + +/** + * Remove a fallback font. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to modify. + * \param fallback the font to remove as a fallback. + * + * \threadsafety This function should be called on the thread that created + * both fonts. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_AddFallbackFont + * \sa TTF_ClearFallbackFonts + */ +extern SDL_DECLSPEC void SDLCALL TTF_RemoveFallbackFont(TTF_Font *font, TTF_Font *fallback); + +/** + * Remove all fallback fonts. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to modify. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_AddFallbackFont + * \sa TTF_RemoveFallbackFont + */ +extern SDL_DECLSPEC void SDLCALL TTF_ClearFallbackFonts(TTF_Font *font); + +/** + * Set a font's size dynamically. + * + * This updates any TTF_Text objects using this font, and clears + * already-generated glyphs, if any, from the cache. + * + * \param font the font to resize. + * \param ptsize the new point size. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontSize + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSize(TTF_Font *font, float ptsize); + +/** + * Set font size dynamically with target resolutions, in dots per inch. + * + * This updates any TTF_Text objects using this font, and clears + * already-generated glyphs, if any, from the cache. + * + * \param font the font to resize. + * \param ptsize the new point size. + * \param hdpi the target horizontal DPI. + * \param vdpi the target vertical DPI. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontSize + * \sa TTF_GetFontSizeDPI + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSizeDPI(TTF_Font *font, float ptsize, int hdpi, int vdpi); + +/** + * Get the size of a font. + * + * \param font the font to query. + * \returns the size of the font, or 0.0f on failure; call SDL_GetError() for + * more information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontSize + * \sa TTF_SetFontSizeDPI + */ +extern SDL_DECLSPEC float SDLCALL TTF_GetFontSize(TTF_Font *font); + +/** + * Get font target resolutions, in dots per inch. + * + * \param font the font to query. + * \param hdpi a pointer filled in with the target horizontal DPI. + * \param vdpi a pointer filled in with the target vertical DPI. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontSizeDPI + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetFontDPI(TTF_Font *font, int *hdpi, int *vdpi); + +/** + * Font style flags for TTF_Font + * + * These are the flags which can be used to set the style of a font in + * SDL_ttf. A combination of these flags can be used with functions that set + * or query font style, such as TTF_SetFontStyle or TTF_GetFontStyle. + * + * \since This datatype is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontStyle + * \sa TTF_GetFontStyle + */ +typedef Uint32 TTF_FontStyleFlags; + +#define TTF_STYLE_NORMAL 0x00 /**< No special style */ +#define TTF_STYLE_BOLD 0x01 /**< Bold style */ +#define TTF_STYLE_ITALIC 0x02 /**< Italic style */ +#define TTF_STYLE_UNDERLINE 0x04 /**< Underlined text */ +#define TTF_STYLE_STRIKETHROUGH 0x08 /**< Strikethrough text */ + +/** + * Set a font's current style. + * + * This updates any TTF_Text objects using this font, and clears + * already-generated glyphs, if any, from the cache. + * + * The font styles are a set of bit flags, OR'd together: + * + * - `TTF_STYLE_NORMAL` (is zero) + * - `TTF_STYLE_BOLD` + * - `TTF_STYLE_ITALIC` + * - `TTF_STYLE_UNDERLINE` + * - `TTF_STYLE_STRIKETHROUGH` + * + * \param font the font to set a new style on. + * \param style the new style values to set, OR'd together. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontStyle + */ +extern SDL_DECLSPEC void SDLCALL TTF_SetFontStyle(TTF_Font *font, TTF_FontStyleFlags style); + +/** + * Query a font's current style. + * + * The font styles are a set of bit flags, OR'd together: + * + * - `TTF_STYLE_NORMAL` (is zero) + * - `TTF_STYLE_BOLD` + * - `TTF_STYLE_ITALIC` + * - `TTF_STYLE_UNDERLINE` + * - `TTF_STYLE_STRIKETHROUGH` + * + * \param font the font to query. + * \returns the current font style, as a set of bit flags. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontStyle + */ +extern SDL_DECLSPEC TTF_FontStyleFlags SDLCALL TTF_GetFontStyle(const TTF_Font *font); + +/** + * Set a font's current outline. + * + * This uses the font properties `TTF_PROP_FONT_OUTLINE_LINE_CAP_NUMBER`, + * `TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER`, and + * `TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER` when setting the font outline. + * + * This updates any TTF_Text objects using this font, and clears + * already-generated glyphs, if any, from the cache. + * + * \param font the font to set a new outline on. + * \param outline positive outline value, 0 to default. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontOutline + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontOutline(TTF_Font *font, int outline); + +/** + * Query a font's current outline. + * + * \param font the font to query. + * \returns the font's current outline value. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontOutline + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetFontOutline(const TTF_Font *font); + +/** + * Hinting flags for TTF (TrueType Fonts) + * + * This enum specifies the level of hinting to be applied to the font + * rendering. The hinting level determines how much the font's outlines are + * adjusted for better alignment on the pixel grid. + * + * \since This enum is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontHinting + * \sa TTF_GetFontHinting + */ +typedef enum TTF_HintingFlags +{ + TTF_HINTING_INVALID = -1, + TTF_HINTING_NORMAL, /**< Normal hinting applies standard grid-fitting. */ + TTF_HINTING_LIGHT, /**< Light hinting applies subtle adjustments to improve rendering. */ + TTF_HINTING_MONO, /**< Monochrome hinting adjusts the font for better rendering at lower resolutions. */ + TTF_HINTING_NONE, /**< No hinting, the font is rendered without any grid-fitting. */ + TTF_HINTING_LIGHT_SUBPIXEL /**< Light hinting with subpixel rendering for more precise font edges. */ +} TTF_HintingFlags; + +/** + * Set a font's current hinter setting. + * + * This updates any TTF_Text objects using this font, and clears + * already-generated glyphs, if any, from the cache. + * + * The hinter setting is a single value: + * + * - `TTF_HINTING_NORMAL` + * - `TTF_HINTING_LIGHT` + * - `TTF_HINTING_MONO` + * - `TTF_HINTING_NONE` + * - `TTF_HINTING_LIGHT_SUBPIXEL` (available in SDL_ttf 3.0.0 and later) + * + * \param font the font to set a new hinter setting on. + * \param hinting the new hinter setting. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontHinting + */ +extern SDL_DECLSPEC void SDLCALL TTF_SetFontHinting(TTF_Font *font, TTF_HintingFlags hinting); + +/** + * Query the number of faces of a font. + * + * \param font the font to query. + * \returns the number of FreeType font faces. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetNumFontFaces(const TTF_Font *font); + +/** + * Query a font's current FreeType hinter setting. + * + * The hinter setting is a single value: + * + * - `TTF_HINTING_NORMAL` + * - `TTF_HINTING_LIGHT` + * - `TTF_HINTING_MONO` + * - `TTF_HINTING_NONE` + * - `TTF_HINTING_LIGHT_SUBPIXEL` (available in SDL_ttf 3.0.0 and later) + * + * \param font the font to query. + * \returns the font's current hinter value, or TTF_HINTING_INVALID if the + * font is invalid. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontHinting + */ +extern SDL_DECLSPEC TTF_HintingFlags SDLCALL TTF_GetFontHinting(const TTF_Font *font); + +/** + * Enable Signed Distance Field rendering for a font. + * + * SDF is a technique that helps fonts look sharp even when scaling and + * rotating, and requires special shader support for display. + * + * This works with Blended APIs, and generates the raw signed distance values + * in the alpha channel of the resulting texture. + * + * This updates any TTF_Text objects using this font, and clears + * already-generated glyphs, if any, from the cache. + * + * \param font the font to set SDF support on. + * \param enabled true to enable SDF, false to disable. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontSDF + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSDF(TTF_Font *font, bool enabled); + +/** + * Query whether Signed Distance Field rendering is enabled for a font. + * + * \param font the font to query. + * \returns true if enabled, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontSDF + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetFontSDF(const TTF_Font *font); + +/** + * Query a font's weight, in terms of the lightness/heaviness of the strokes. + * + * \param font the font to query. + * \returns the font's current weight. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.4.0. + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetFontWeight(const TTF_Font *font); + +#define TTF_FONT_WEIGHT_THIN 100 /**< Thin (100) named font weight value */ +#define TTF_FONT_WEIGHT_EXTRA_LIGHT 200 /**< ExtraLight (200) named font weight value */ +#define TTF_FONT_WEIGHT_LIGHT 300 /**< Light (300) named font weight value */ +#define TTF_FONT_WEIGHT_NORMAL 400 /**< Normal (400) named font weight value */ +#define TTF_FONT_WEIGHT_MEDIUM 500 /**< Medium (500) named font weight value */ +#define TTF_FONT_WEIGHT_SEMI_BOLD 600 /**< SemiBold (600) named font weight value */ +#define TTF_FONT_WEIGHT_BOLD 700 /**< Bold (700) named font weight value */ +#define TTF_FONT_WEIGHT_EXTRA_BOLD 800 /**< ExtraBold (800) named font weight value */ +#define TTF_FONT_WEIGHT_BLACK 900 /**< Black (900) named font weight value */ +#define TTF_FONT_WEIGHT_EXTRA_BLACK 950 /**< ExtraBlack (950) named font weight value */ + +/** + * The horizontal alignment used when rendering wrapped text. + * + * \since This enum is available since SDL_ttf 3.0.0. + */ +typedef enum TTF_HorizontalAlignment +{ + TTF_HORIZONTAL_ALIGN_INVALID = -1, + TTF_HORIZONTAL_ALIGN_LEFT, + TTF_HORIZONTAL_ALIGN_CENTER, + TTF_HORIZONTAL_ALIGN_RIGHT +} TTF_HorizontalAlignment; + +/** + * Set a font's current wrap alignment option. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to set a new wrap alignment option on. + * \param align the new wrap alignment option. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontWrapAlignment + */ +extern SDL_DECLSPEC void SDLCALL TTF_SetFontWrapAlignment(TTF_Font *font, TTF_HorizontalAlignment align); + +/** + * Query a font's current wrap alignment option. + * + * \param font the font to query. + * \returns the font's current wrap alignment option. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontWrapAlignment + */ +extern SDL_DECLSPEC TTF_HorizontalAlignment SDLCALL TTF_GetFontWrapAlignment(const TTF_Font *font); + +/** + * Query the total height of a font. + * + * This is usually equal to point size. + * + * \param font the font to query. + * \returns the font's height. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetFontHeight(const TTF_Font *font); + +/** + * Query the offset from the baseline to the top of a font. + * + * This is a positive value, relative to the baseline. + * + * \param font the font to query. + * \returns the font's ascent. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetFontAscent(const TTF_Font *font); + +/** + * Query the offset from the baseline to the bottom of a font. + * + * This is a negative value, relative to the baseline. + * + * \param font the font to query. + * \returns the font's descent. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetFontDescent(const TTF_Font *font); + +/** + * Set the spacing between lines of text for a font. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to modify. + * \param lineskip the new line spacing for the font. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontLineSkip + */ +extern SDL_DECLSPEC void SDLCALL TTF_SetFontLineSkip(TTF_Font *font, int lineskip); + +/** + * Query the spacing between lines of text for a font. + * + * \param font the font to query. + * \returns the font's recommended spacing. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontLineSkip + */ +extern SDL_DECLSPEC int SDLCALL TTF_GetFontLineSkip(const TTF_Font *font); + +/** + * Set if kerning is enabled for a font. + * + * Newly-opened fonts default to allowing kerning. This is generally a good + * policy unless you have a strong reason to disable it, as it tends to + * produce better rendering (with kerning disabled, some fonts might render + * the word `kerning` as something that looks like `keming` for example). + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to set kerning on. + * \param enabled true to enable kerning, false to disable. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetFontKerning + */ +extern SDL_DECLSPEC void SDLCALL TTF_SetFontKerning(TTF_Font *font, bool enabled); + +/** + * Query whether or not kerning is enabled for a font. + * + * \param font the font to query. + * \returns true if kerning is enabled, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontKerning + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetFontKerning(const TTF_Font *font); + +/** + * Query whether a font is fixed-width. + * + * A "fixed-width" font means all glyphs are the same width across; a + * lowercase 'i' will be the same size across as a capital 'W', for example. + * This is common for terminals and text editors, and other apps that treat + * text as a grid. Most other things (WYSIWYG word processors, web pages, etc) + * are more likely to not be fixed-width in most cases. + * + * \param font the font to query. + * \returns true if the font is fixed-width, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_FontIsFixedWidth(const TTF_Font *font); + +/** + * Query whether a font is scalable or not. + * + * Scalability lets us distinguish between outline and bitmap fonts. + * + * \param font the font to query. + * \returns true if the font is scalable, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontSDF + */ +extern SDL_DECLSPEC bool SDLCALL TTF_FontIsScalable(const TTF_Font *font); + +/** + * Query a font's family name. + * + * This string is dictated by the contents of the font file. + * + * Note that the returned string is to internal storage, and should not be + * modified or free'd by the caller. The string becomes invalid, with the rest + * of the font, when `font` is handed to TTF_CloseFont(). + * + * \param font the font to query. + * \returns the font's family name. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC const char * SDLCALL TTF_GetFontFamilyName(const TTF_Font *font); + +/** + * Query a font's style name. + * + * This string is dictated by the contents of the font file. + * + * Note that the returned string is to internal storage, and should not be + * modified or free'd by the caller. The string becomes invalid, with the rest + * of the font, when `font` is handed to TTF_CloseFont(). + * + * \param font the font to query. + * \returns the font's style name. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC const char * SDLCALL TTF_GetFontStyleName(const TTF_Font *font); + +/** + * Direction flags + * + * The values here are chosen to match + * [hb_direction_t](https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-direction-t) + * . + * + * \since This enum is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetFontDirection + */ +typedef enum TTF_Direction +{ + TTF_DIRECTION_INVALID = 0, + TTF_DIRECTION_LTR = 4, /**< Left to Right */ + TTF_DIRECTION_RTL, /**< Right to Left */ + TTF_DIRECTION_TTB, /**< Top to Bottom */ + TTF_DIRECTION_BTT /**< Bottom to Top */ +} TTF_Direction; + +/** + * Set the direction to be used for text shaping by a font. + * + * This function only supports left-to-right text shaping if SDL_ttf was not + * built with HarfBuzz support. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to modify. + * \param direction the new direction for text to flow. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontDirection(TTF_Font *font, TTF_Direction direction); + +/** + * Get the direction to be used for text shaping by a font. + * + * This defaults to TTF_DIRECTION_INVALID if it hasn't been set. + * + * \param font the font to query. + * \returns the direction to be used for text shaping. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC TTF_Direction SDLCALL TTF_GetFontDirection(TTF_Font *font); + +/** + * Convert from a 4 character string to a 32-bit tag. + * + * \param string the 4 character string to convert. + * \returns the 32-bit representation of the string. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_TagToString + */ +extern SDL_DECLSPEC Uint32 SDLCALL TTF_StringToTag(const char *string); + +/** + * Convert from a 32-bit tag to a 4 character string. + * + * \param tag the 32-bit tag to convert. + * \param string a pointer filled in with the 4 character representation of + * the tag. + * \param size the size of the buffer pointed at by string, should be at least + * 4. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_TagToString + */ +extern SDL_DECLSPEC void SDLCALL TTF_TagToString(Uint32 tag, char *string, size_t size); + +/** + * Set the script to be used for text shaping by a font. + * + * This returns false if SDL_ttf isn't built with HarfBuzz support. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to modify. + * \param script an + * [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) + * . + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_StringToTag + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontScript(TTF_Font *font, Uint32 script); + +/** + * Get the script used for text shaping a font. + * + * \param font the font to query. + * \returns an + * [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) + * or 0 if a script hasn't been set. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_TagToString + */ +extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetFontScript(TTF_Font *font); + +/** + * Get the script used by a 32-bit codepoint. + * + * \param ch the character code to check. + * \returns an + * [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) + * on success, or 0 on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function is thread-safe. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_TagToString + */ +extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetGlyphScript(Uint32 ch); + +/** + * Set language to be used for text shaping by a font. + * + * If SDL_ttf was not built with HarfBuzz support, this function returns + * false. + * + * This updates any TTF_Text objects using this font. + * + * \param font the font to specify a language for. + * \param language_bcp47 a null-terminated string containing the desired + * language's BCP47 code. Or null to reset the value. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetFontLanguage(TTF_Font *font, const char *language_bcp47); + +/** + * Check whether a glyph is provided by the font for a UNICODE codepoint. + * + * \param font the font to query. + * \param ch the codepoint to check. + * \returns true if font provides a glyph for this character, false if not. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_FontHasGlyph(TTF_Font *font, Uint32 ch); + +/** + * The type of data in a glyph image + * + * \since This enum is available since SDL_ttf 3.0.0. + */ +typedef enum TTF_ImageType +{ + TTF_IMAGE_INVALID, + TTF_IMAGE_ALPHA, /**< The color channels are white */ + TTF_IMAGE_COLOR, /**< The color channels have image data */ + TTF_IMAGE_SDF, /**< The alpha channel has signed distance field information */ +} TTF_ImageType; + +/** + * Get the pixel image for a UNICODE codepoint. + * + * \param font the font to query. + * \param ch the codepoint to check. + * \param image_type a pointer filled in with the glyph image type, may be + * NULL. + * \returns an SDL_Surface containing the glyph, or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_GetGlyphImage(TTF_Font *font, Uint32 ch, TTF_ImageType *image_type); + +/** + * Get the pixel image for a character index. + * + * This is useful for text engine implementations, which can call this with + * the `glyph_index` in a TTF_CopyOperation + * + * \param font the font to query. + * \param glyph_index the index of the glyph to return. + * \param image_type a pointer filled in with the glyph image type, may be + * NULL. + * \returns an SDL_Surface containing the glyph, or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_GetGlyphImageForIndex(TTF_Font *font, Uint32 glyph_index, TTF_ImageType *image_type); + +/** + * Query the metrics (dimensions) of a font's glyph for a UNICODE codepoint. + * + * To understand what these metrics mean, here is a useful link: + * + * https://freetype.sourceforge.net/freetype2/docs/tutorial/step2.html + * + * \param font the font to query. + * \param ch the codepoint to check. + * \param minx a pointer filled in with the minimum x coordinate of the glyph + * from the left edge of its bounding box. This value may be + * negative. + * \param maxx a pointer filled in with the maximum x coordinate of the glyph + * from the left edge of its bounding box. + * \param miny a pointer filled in with the minimum y coordinate of the glyph + * from the bottom edge of its bounding box. This value may be + * negative. + * \param maxy a pointer filled in with the maximum y coordinate of the glyph + * from the bottom edge of its bounding box. + * \param advance a pointer filled in with the distance to the next glyph from + * the left edge of this glyph's bounding box. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetGlyphMetrics(TTF_Font *font, Uint32 ch, int *minx, int *maxx, int *miny, int *maxy, int *advance); + +/** + * Query the kerning size between the glyphs of two UNICODE codepoints. + * + * \param font the font to query. + * \param previous_ch the previous codepoint. + * \param ch the current codepoint. + * \param kerning a pointer filled in with the kerning size between the two + * glyphs, in pixels, may be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetGlyphKerning(TTF_Font *font, Uint32 previous_ch, Uint32 ch, int *kerning); + +/** + * Calculate the dimensions of a rendered string of UTF-8 text. + * + * This will report the width and height, in pixels, of the space that the + * specified string will take to fully render. + * + * \param font the font to query. + * \param text text to calculate, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param w will be filled with width, in pixels, on return. + * \param h will be filled with height, in pixels, on return. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetStringSize(TTF_Font *font, const char *text, size_t length, int *w, int *h); + +/** + * Calculate the dimensions of a rendered string of UTF-8 text. + * + * This will report the width and height, in pixels, of the space that the + * specified string will take to fully render. + * + * Text is wrapped to multiple lines on line endings and on word boundaries if + * it extends beyond `wrap_width` in pixels. + * + * If wrap_width is 0, this function will only wrap on newline characters. + * + * \param font the font to query. + * \param text text to calculate, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param wrap_width the maximum width or 0 to wrap on newline characters. + * \param w will be filled with width, in pixels, on return. + * \param h will be filled with height, in pixels, on return. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetStringSizeWrapped(TTF_Font *font, const char *text, size_t length, int wrap_width, int *w, int *h); + +/** + * Calculate how much of a UTF-8 string will fit in a given width. + * + * This reports the number of characters that can be rendered before reaching + * `max_width`. + * + * This does not need to render the string to do this calculation. + * + * \param font the font to query. + * \param text text to calculate, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param max_width maximum width, in pixels, available for the string, or 0 + * for unbounded width. + * \param measured_width a pointer filled in with the width, in pixels, of the + * string that will fit, may be NULL. + * \param measured_length a pointer filled in with the length, in bytes, of + * the string that will fit, may be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_MeasureString(TTF_Font *font, const char *text, size_t length, int max_width, int *measured_width, size_t *measured_length); + +/** + * Render UTF-8 text at fast quality to a new 8-bit surface. + * + * This function will allocate a new 8-bit, palettized surface. The surface's + * 0 pixel will be the colorkey, giving a transparent background. The 1 pixel + * will be set to the text color. + * + * This will not word-wrap the string; you'll get a surface with a single line + * of text, as long as the string requires. You can use + * TTF_RenderText_Solid_Wrapped() instead if you need to wrap the output to + * multiple lines. + * + * This will not wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Shaded, + * TTF_RenderText_Blended, and TTF_RenderText_LCD. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \returns a new 8-bit, palettized surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended + * \sa TTF_RenderText_LCD + * \sa TTF_RenderText_Shaded + * \sa TTF_RenderText_Solid + * \sa TTF_RenderText_Solid_Wrapped + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Solid(TTF_Font *font, const char *text, size_t length, SDL_Color fg); + +/** + * Render word-wrapped UTF-8 text at fast quality to a new 8-bit surface. + * + * This function will allocate a new 8-bit, palettized surface. The surface's + * 0 pixel will be the colorkey, giving a transparent background. The 1 pixel + * will be set to the text color. + * + * Text is wrapped to multiple lines on line endings and on word boundaries if + * it extends beyond `wrapLength` in pixels. + * + * If wrapLength is 0, this function will only wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Shaded_Wrapped, + * TTF_RenderText_Blended_Wrapped, and TTF_RenderText_LCD_Wrapped. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \param wrapLength the maximum width of the text surface or 0 to wrap on + * newline characters. + * \returns a new 8-bit, palettized surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended_Wrapped + * \sa TTF_RenderText_LCD_Wrapped + * \sa TTF_RenderText_Shaded_Wrapped + * \sa TTF_RenderText_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Solid_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, int wrapLength); + +/** + * Render a single 32-bit glyph at fast quality to a new 8-bit surface. + * + * This function will allocate a new 8-bit, palettized surface. The surface's + * 0 pixel will be the colorkey, giving a transparent background. The 1 pixel + * will be set to the text color. + * + * The glyph is rendered without any padding or centering in the X direction, + * and aligned normally in the Y direction. + * + * You can render at other quality levels with TTF_RenderGlyph_Shaded, + * TTF_RenderGlyph_Blended, and TTF_RenderGlyph_LCD. + * + * \param font the font to render with. + * \param ch the character to render. + * \param fg the foreground color for the text. + * \returns a new 8-bit, palettized surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderGlyph_Blended + * \sa TTF_RenderGlyph_LCD + * \sa TTF_RenderGlyph_Shaded + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Solid(TTF_Font *font, Uint32 ch, SDL_Color fg); + +/** + * Render UTF-8 text at high quality to a new 8-bit surface. + * + * This function will allocate a new 8-bit, palettized surface. The surface's + * 0 pixel will be the specified background color, while other pixels have + * varying degrees of the foreground color. This function returns the new + * surface, or NULL if there was an error. + * + * This will not word-wrap the string; you'll get a surface with a single line + * of text, as long as the string requires. You can use + * TTF_RenderText_Shaded_Wrapped() instead if you need to wrap the output to + * multiple lines. + * + * This will not wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Solid, + * TTF_RenderText_Blended, and TTF_RenderText_LCD. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \param bg the background color for the text. + * \returns a new 8-bit, palettized surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended + * \sa TTF_RenderText_LCD + * \sa TTF_RenderText_Shaded_Wrapped + * \sa TTF_RenderText_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Shaded(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg); + +/** + * Render word-wrapped UTF-8 text at high quality to a new 8-bit surface. + * + * This function will allocate a new 8-bit, palettized surface. The surface's + * 0 pixel will be the specified background color, while other pixels have + * varying degrees of the foreground color. This function returns the new + * surface, or NULL if there was an error. + * + * Text is wrapped to multiple lines on line endings and on word boundaries if + * it extends beyond `wrap_width` in pixels. + * + * If wrap_width is 0, this function will only wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Solid_Wrapped, + * TTF_RenderText_Blended_Wrapped, and TTF_RenderText_LCD_Wrapped. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \param bg the background color for the text. + * \param wrap_width the maximum width of the text surface or 0 to wrap on + * newline characters. + * \returns a new 8-bit, palettized surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended_Wrapped + * \sa TTF_RenderText_LCD_Wrapped + * \sa TTF_RenderText_Shaded + * \sa TTF_RenderText_Solid_Wrapped + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Shaded_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrap_width); + +/** + * Render a single UNICODE codepoint at high quality to a new 8-bit surface. + * + * This function will allocate a new 8-bit, palettized surface. The surface's + * 0 pixel will be the specified background color, while other pixels have + * varying degrees of the foreground color. This function returns the new + * surface, or NULL if there was an error. + * + * The glyph is rendered without any padding or centering in the X direction, + * and aligned normally in the Y direction. + * + * You can render at other quality levels with TTF_RenderGlyph_Solid, + * TTF_RenderGlyph_Blended, and TTF_RenderGlyph_LCD. + * + * \param font the font to render with. + * \param ch the codepoint to render. + * \param fg the foreground color for the text. + * \param bg the background color for the text. + * \returns a new 8-bit, palettized surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderGlyph_Blended + * \sa TTF_RenderGlyph_LCD + * \sa TTF_RenderGlyph_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Shaded(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg); + +/** + * Render UTF-8 text at high quality to a new ARGB surface. + * + * This function will allocate a new 32-bit, ARGB surface, using alpha + * blending to dither the font with the given color. This function returns the + * new surface, or NULL if there was an error. + * + * This will not word-wrap the string; you'll get a surface with a single line + * of text, as long as the string requires. You can use + * TTF_RenderText_Blended_Wrapped() instead if you need to wrap the output to + * multiple lines. + * + * This will not wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Solid, + * TTF_RenderText_Shaded, and TTF_RenderText_LCD. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \returns a new 32-bit, ARGB surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended_Wrapped + * \sa TTF_RenderText_LCD + * \sa TTF_RenderText_Shaded + * \sa TTF_RenderText_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Blended(TTF_Font *font, const char *text, size_t length, SDL_Color fg); + +/** + * Render word-wrapped UTF-8 text at high quality to a new ARGB surface. + * + * This function will allocate a new 32-bit, ARGB surface, using alpha + * blending to dither the font with the given color. This function returns the + * new surface, or NULL if there was an error. + * + * Text is wrapped to multiple lines on line endings and on word boundaries if + * it extends beyond `wrap_width` in pixels. + * + * If wrap_width is 0, this function will only wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Solid_Wrapped, + * TTF_RenderText_Shaded_Wrapped, and TTF_RenderText_LCD_Wrapped. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \param wrap_width the maximum width of the text surface or 0 to wrap on + * newline characters. + * \returns a new 32-bit, ARGB surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended + * \sa TTF_RenderText_LCD_Wrapped + * \sa TTF_RenderText_Shaded_Wrapped + * \sa TTF_RenderText_Solid_Wrapped + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Blended_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, int wrap_width); + +/** + * Render a single UNICODE codepoint at high quality to a new ARGB surface. + * + * This function will allocate a new 32-bit, ARGB surface, using alpha + * blending to dither the font with the given color. This function returns the + * new surface, or NULL if there was an error. + * + * The glyph is rendered without any padding or centering in the X direction, + * and aligned normally in the Y direction. + * + * You can render at other quality levels with TTF_RenderGlyph_Solid, + * TTF_RenderGlyph_Shaded, and TTF_RenderGlyph_LCD. + * + * \param font the font to render with. + * \param ch the codepoint to render. + * \param fg the foreground color for the text. + * \returns a new 32-bit, ARGB surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderGlyph_LCD + * \sa TTF_RenderGlyph_Shaded + * \sa TTF_RenderGlyph_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Blended(TTF_Font *font, Uint32 ch, SDL_Color fg); + +/** + * Render UTF-8 text at LCD subpixel quality to a new ARGB surface. + * + * This function will allocate a new 32-bit, ARGB surface, and render + * alpha-blended text using FreeType's LCD subpixel rendering. This function + * returns the new surface, or NULL if there was an error. + * + * This will not word-wrap the string; you'll get a surface with a single line + * of text, as long as the string requires. You can use + * TTF_RenderText_LCD_Wrapped() instead if you need to wrap the output to + * multiple lines. + * + * This will not wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Solid, + * TTF_RenderText_Shaded, and TTF_RenderText_Blended. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \param bg the background color for the text. + * \returns a new 32-bit, ARGB surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended + * \sa TTF_RenderText_LCD_Wrapped + * \sa TTF_RenderText_Shaded + * \sa TTF_RenderText_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_LCD(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg); + +/** + * Render word-wrapped UTF-8 text at LCD subpixel quality to a new ARGB + * surface. + * + * This function will allocate a new 32-bit, ARGB surface, and render + * alpha-blended text using FreeType's LCD subpixel rendering. This function + * returns the new surface, or NULL if there was an error. + * + * Text is wrapped to multiple lines on line endings and on word boundaries if + * it extends beyond `wrap_width` in pixels. + * + * If wrap_width is 0, this function will only wrap on newline characters. + * + * You can render at other quality levels with TTF_RenderText_Solid_Wrapped, + * TTF_RenderText_Shaded_Wrapped, and TTF_RenderText_Blended_Wrapped. + * + * \param font the font to render with. + * \param text text to render, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \param fg the foreground color for the text. + * \param bg the background color for the text. + * \param wrap_width the maximum width of the text surface or 0 to wrap on + * newline characters. + * \returns a new 32-bit, ARGB surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderText_Blended_Wrapped + * \sa TTF_RenderText_LCD + * \sa TTF_RenderText_Shaded_Wrapped + * \sa TTF_RenderText_Solid_Wrapped + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_LCD_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrap_width); + +/** + * Render a single UNICODE codepoint at LCD subpixel quality to a new ARGB + * surface. + * + * This function will allocate a new 32-bit, ARGB surface, and render + * alpha-blended text using FreeType's LCD subpixel rendering. This function + * returns the new surface, or NULL if there was an error. + * + * The glyph is rendered without any padding or centering in the X direction, + * and aligned normally in the Y direction. + * + * You can render at other quality levels with TTF_RenderGlyph_Solid, + * TTF_RenderGlyph_Shaded, and TTF_RenderGlyph_Blended. + * + * \param font the font to render with. + * \param ch the codepoint to render. + * \param fg the foreground color for the text. + * \param bg the background color for the text. + * \returns a new 32-bit, ARGB surface, or NULL if there was an error. + * + * \threadsafety This function should be called on the thread that created the + * font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_RenderGlyph_Blended + * \sa TTF_RenderGlyph_Shaded + * \sa TTF_RenderGlyph_Solid + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_LCD(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg); + + +/** + * A text engine used to create text objects. + * + * This is a public interface that can be used by applications and libraries + * to perform customize rendering with text objects. See + * for details. + * + * There are three text engines provided with the library: + * + * - Drawing to an SDL_Surface, created with TTF_CreateSurfaceTextEngine() + * - Drawing with an SDL 2D renderer, created with + * TTF_CreateRendererTextEngine() + * - Drawing with the SDL GPU API, created with TTF_CreateGPUTextEngine() + * + * \since This struct is available since SDL_ttf 3.0.0. + */ +typedef struct TTF_TextEngine TTF_TextEngine; + +/** + * Internal data for TTF_Text + * + * \since This struct is available since SDL_ttf 3.0.0. + */ +typedef struct TTF_TextData TTF_TextData; + +/** + * Text created with TTF_CreateText() + * + * \since This struct is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateText + * \sa TTF_GetTextProperties + * \sa TTF_DestroyText + */ +typedef struct TTF_Text +{ + char *text; /**< A copy of the UTF-8 string that this text object represents, useful for layout, debugging and retrieving substring text. This is updated when the text object is modified and will be freed automatically when the object is destroyed. */ + int num_lines; /**< The number of lines in the text, 0 if it's empty */ + + int refcount; /**< Application reference count, used when freeing surface */ + + TTF_TextData *internal; /**< Private */ + +} TTF_Text; + +/** + * Create a text engine for drawing text on SDL surfaces. + * + * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_DestroySurfaceTextEngine + * \sa TTF_DrawSurfaceText + */ +extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateSurfaceTextEngine(void); + +/** + * Draw text to an SDL surface. + * + * `text` must have been created using a TTF_TextEngine from + * TTF_CreateSurfaceTextEngine(). + * + * \param text the text to draw. + * \param x the x coordinate in pixels, positive from the left edge towards + * the right. + * \param y the y coordinate in pixels, positive from the top edge towards the + * bottom. + * \param surface the surface to draw on. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateSurfaceTextEngine + * \sa TTF_CreateText + */ +extern SDL_DECLSPEC bool SDLCALL TTF_DrawSurfaceText(TTF_Text *text, int x, int y, SDL_Surface *surface); + +/** + * Destroy a text engine created for drawing text on SDL surfaces. + * + * All text created by this engine should be destroyed before calling this + * function. + * + * \param engine a TTF_TextEngine object created with + * TTF_CreateSurfaceTextEngine(). + * + * \threadsafety This function should be called on the thread that created the + * engine. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateSurfaceTextEngine + */ +extern SDL_DECLSPEC void SDLCALL TTF_DestroySurfaceTextEngine(TTF_TextEngine *engine); + +/** + * Create a text engine for drawing text on an SDL renderer. + * + * \param renderer the renderer to use for creating textures and drawing text. + * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_DestroyRendererTextEngine + * \sa TTF_DrawRendererText + * \sa TTF_CreateRendererTextEngineWithProperties + */ +extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateRendererTextEngine(SDL_Renderer *renderer); + +/** + * Create a text engine for drawing text on an SDL renderer, with the + * specified properties. + * + * These are the supported properties: + * + * - `TTF_PROP_RENDERER_TEXT_ENGINE_RENDERER`: the renderer to use for + * creating textures and drawing text + * - `TTF_PROP_RENDERER_TEXT_ENGINE_ATLAS_TEXTURE_SIZE`: the size of the + * texture atlas + * + * \param props the properties to use. + * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateRendererTextEngine + * \sa TTF_DestroyRendererTextEngine + * \sa TTF_DrawRendererText + */ +extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateRendererTextEngineWithProperties(SDL_PropertiesID props); + +#define TTF_PROP_RENDERER_TEXT_ENGINE_RENDERER "SDL_ttf.renderer_text_engine.create.renderer" +#define TTF_PROP_RENDERER_TEXT_ENGINE_ATLAS_TEXTURE_SIZE "SDL_ttf.renderer_text_engine.create.atlas_texture_size" + +/** + * Draw text to an SDL renderer. + * + * `text` must have been created using a TTF_TextEngine from + * TTF_CreateRendererTextEngine(), and will draw using the renderer passed to + * that function. + * + * \param text the text to draw. + * \param x the x coordinate in pixels, positive from the left edge towards + * the right. + * \param y the y coordinate in pixels, positive from the top edge towards the + * bottom. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateRendererTextEngine + * \sa TTF_CreateText + */ +extern SDL_DECLSPEC bool SDLCALL TTF_DrawRendererText(TTF_Text *text, float x, float y); + +/** + * Destroy a text engine created for drawing text on an SDL renderer. + * + * All text created by this engine should be destroyed before calling this + * function. + * + * \param engine a TTF_TextEngine object created with + * TTF_CreateRendererTextEngine(). + * + * \threadsafety This function should be called on the thread that created the + * engine. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateRendererTextEngine + */ +extern SDL_DECLSPEC void SDLCALL TTF_DestroyRendererTextEngine(TTF_TextEngine *engine); + +/** + * Create a text engine for drawing text with the SDL GPU API. + * + * \param device the SDL_GPUDevice to use for creating textures and drawing + * text. + * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should be called on the thread that created the + * device. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateGPUTextEngineWithProperties + * \sa TTF_DestroyGPUTextEngine + * \sa TTF_GetGPUTextDrawData + */ +extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateGPUTextEngine(SDL_GPUDevice *device); + +/** + * Create a text engine for drawing text with the SDL GPU API, with the + * specified properties. + * + * These are the supported properties: + * + * - `TTF_PROP_GPU_TEXT_ENGINE_DEVICE`: the SDL_GPUDevice to use for creating + * textures and drawing text. + * - `TTF_PROP_GPU_TEXT_ENGINE_ATLAS_TEXTURE_SIZE`: the size of the texture + * atlas + * + * \param props the properties to use. + * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should be called on the thread that created the + * device. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateGPUTextEngine + * \sa TTF_DestroyGPUTextEngine + * \sa TTF_GetGPUTextDrawData + */ +extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateGPUTextEngineWithProperties(SDL_PropertiesID props); + +#define TTF_PROP_GPU_TEXT_ENGINE_DEVICE "SDL_ttf.gpu_text_engine.create.device" +#define TTF_PROP_GPU_TEXT_ENGINE_ATLAS_TEXTURE_SIZE "SDL_ttf.gpu_text_engine.create.atlas_texture_size" + +/** + * Draw sequence returned by TTF_GetGPUTextDrawData + * + * \since This struct is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetGPUTextDrawData + */ +typedef struct TTF_GPUAtlasDrawSequence +{ + SDL_GPUTexture *atlas_texture; /**< Texture atlas that stores the glyphs */ + SDL_FPoint *xy; /**< An array of vertex positions */ + SDL_FPoint *uv; /**< An array of normalized texture coordinates for each vertex */ + int num_vertices; /**< Number of vertices */ + int *indices; /**< An array of indices into the 'vertices' arrays */ + int num_indices; /**< Number of indices */ + TTF_ImageType image_type; /**< The image type of this draw sequence */ + + struct TTF_GPUAtlasDrawSequence *next; /**< The next sequence (will be NULL in case of the last sequence) */ +} TTF_GPUAtlasDrawSequence; + +/** + * Get the geometry data needed for drawing the text. + * + * `text` must have been created using a TTF_TextEngine from + * TTF_CreateGPUTextEngine(). + * + * The positive X-axis is taken towards the right and the positive Y-axis is + * taken upwards for both the vertex and the texture coordinates, i.e, it + * follows the same convention used by the SDL_GPU API. If you want to use a + * different coordinate system you will need to transform the vertices + * yourself. + * + * If the text looks blocky use linear filtering. + * + * \param text the text to draw. + * \returns a NULL terminated linked list of TTF_GPUAtlasDrawSequence objects + * or NULL if the passed text is empty or in case of failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateGPUTextEngine + * \sa TTF_CreateText + */ +extern SDL_DECLSPEC TTF_GPUAtlasDrawSequence * SDLCALL TTF_GetGPUTextDrawData(TTF_Text *text); + +/** + * Destroy a text engine created for drawing text with the SDL GPU API. + * + * All text created by this engine should be destroyed before calling this + * function. + * + * \param engine a TTF_TextEngine object created with + * TTF_CreateGPUTextEngine(). + * + * \threadsafety This function should be called on the thread that created the + * engine. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateGPUTextEngine + */ +extern SDL_DECLSPEC void SDLCALL TTF_DestroyGPUTextEngine(TTF_TextEngine *engine); + +/** + * The winding order of the vertices returned by TTF_GetGPUTextDrawData + * + * \since This enum is available since SDL_ttf 3.0.0. + */ +typedef enum TTF_GPUTextEngineWinding +{ + TTF_GPU_TEXTENGINE_WINDING_INVALID = -1, + TTF_GPU_TEXTENGINE_WINDING_CLOCKWISE, + TTF_GPU_TEXTENGINE_WINDING_COUNTER_CLOCKWISE +} TTF_GPUTextEngineWinding; + +/** + * Sets the winding order of the vertices returned by TTF_GetGPUTextDrawData + * for a particular GPU text engine. + * + * \param engine a TTF_TextEngine object created with + * TTF_CreateGPUTextEngine(). + * \param winding the new winding order option. + * + * \threadsafety This function should be called on the thread that created the + * engine. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetGPUTextEngineWinding + */ +extern SDL_DECLSPEC void SDLCALL TTF_SetGPUTextEngineWinding(TTF_TextEngine *engine, TTF_GPUTextEngineWinding winding); + +/** + * Get the winding order of the vertices returned by TTF_GetGPUTextDrawData + * for a particular GPU text engine + * + * \param engine a TTF_TextEngine object created with + * TTF_CreateGPUTextEngine(). + * \returns the winding order used by the GPU text engine or + * TTF_GPU_TEXTENGINE_WINDING_INVALID in case of error. + * + * \threadsafety This function should be called on the thread that created the + * engine. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetGPUTextEngineWinding + */ +extern SDL_DECLSPEC TTF_GPUTextEngineWinding SDLCALL TTF_GetGPUTextEngineWinding(const TTF_TextEngine *engine); + +/** + * Create a text object from UTF-8 text and a text engine. + * + * \param engine the text engine to use when creating the text object, may be + * NULL. + * \param font the font to render with. + * \param text the text to use, in UTF-8 encoding. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \returns a TTF_Text object or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * font and text engine. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_DestroyText + */ +extern SDL_DECLSPEC TTF_Text * SDLCALL TTF_CreateText(TTF_TextEngine *engine, TTF_Font *font, const char *text, size_t length); + +/** + * Get the properties associated with a text object. + * + * \param text the TTF_Text to query. + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL TTF_GetTextProperties(TTF_Text *text); + +/** + * Set the text engine used by a text object. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param engine the text engine to use for drawing. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextEngine + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextEngine(TTF_Text *text, TTF_TextEngine *engine); + +/** + * Get the text engine used by a text object. + * + * \param text the TTF_Text to query. + * \returns the TTF_TextEngine used by the text on success or NULL on failure; + * call SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetTextEngine + */ +extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_GetTextEngine(TTF_Text *text); + +/** + * Set the font used by a text object. + * + * When a text object has a font, any changes to the font will automatically + * regenerate the text. If you set the font to NULL, the text will continue to + * render but changes to the font will no longer affect the text. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param font the font to use, may be NULL. + * \returns false if the text pointer is null; otherwise, true. call + * SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextFont + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextFont(TTF_Text *text, TTF_Font *font); + +/** + * Get the font used by a text object. + * + * \param text the TTF_Text to query. + * \returns the TTF_Font used by the text on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetTextFont + */ +extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_GetTextFont(TTF_Text *text); + +/** + * Set the direction to be used for text shaping a text object. + * + * This function only supports left-to-right text shaping if SDL_ttf was not + * built with HarfBuzz support. + * + * \param text the text to modify. + * \param direction the new direction for text to flow. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextDirection(TTF_Text *text, TTF_Direction direction); + +/** + * Get the direction to be used for text shaping a text object. + * + * This defaults to the direction of the font used by the text object. + * + * \param text the text to query. + * \returns the direction to be used for text shaping. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC TTF_Direction SDLCALL TTF_GetTextDirection(TTF_Text *text); + +/** + * Set the script to be used for text shaping a text object. + * + * This returns false if SDL_ttf isn't built with HarfBuzz support. + * + * \param text the text to modify. + * \param script an + * [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) + * . + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_StringToTag + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextScript(TTF_Text *text, Uint32 script); + +/** + * Get the script used for text shaping a text object. + * + * This defaults to the script of the font used by the text object. + * + * \param text the text to query. + * \returns an + * [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) + * or 0 if a script hasn't been set on either the text object or the + * font. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_TagToString + */ +extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetTextScript(TTF_Text *text); + +/** + * Set the color of a text object. + * + * The default text color is white (255, 255, 255, 255). + * + * \param text the TTF_Text to modify. + * \param r the red color value in the range of 0-255. + * \param g the green color value in the range of 0-255. + * \param b the blue color value in the range of 0-255. + * \param a the alpha value in the range of 0-255. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextColor + * \sa TTF_SetTextColorFloat + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextColor(TTF_Text *text, Uint8 r, Uint8 g, Uint8 b, Uint8 a); + +/** + * Set the color of a text object. + * + * The default text color is white (1.0f, 1.0f, 1.0f, 1.0f). + * + * \param text the TTF_Text to modify. + * \param r the red color value, normally in the range of 0-1. + * \param g the green color value, normally in the range of 0-1. + * \param b the blue color value, normally in the range of 0-1. + * \param a the alpha value in the range of 0-1. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextColorFloat + * \sa TTF_SetTextColor + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextColorFloat(TTF_Text *text, float r, float g, float b, float a); + +/** + * Get the color of a text object. + * + * \param text the TTF_Text to query. + * \param r a pointer filled in with the red color value in the range of + * 0-255, may be NULL. + * \param g a pointer filled in with the green color value in the range of + * 0-255, may be NULL. + * \param b a pointer filled in with the blue color value in the range of + * 0-255, may be NULL. + * \param a a pointer filled in with the alpha value in the range of 0-255, + * may be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextColorFloat + * \sa TTF_SetTextColor + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextColor(TTF_Text *text, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); + +/** + * Get the color of a text object. + * + * \param text the TTF_Text to query. + * \param r a pointer filled in with the red color value, normally in the + * range of 0-1, may be NULL. + * \param g a pointer filled in with the green color value, normally in the + * range of 0-1, may be NULL. + * \param b a pointer filled in with the blue color value, normally in the + * range of 0-1, may be NULL. + * \param a a pointer filled in with the alpha value in the range of 0-1, may + * be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextColor + * \sa TTF_SetTextColorFloat + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextColorFloat(TTF_Text *text, float *r, float *g, float *b, float *a); + +/** + * Set the position of a text object. + * + * This can be used to position multiple text objects within a single wrapping + * text area. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param x the x offset of the upper left corner of this text in pixels. + * \param y the y offset of the upper left corner of this text in pixels. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextPosition + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextPosition(TTF_Text *text, int x, int y); + +/** + * Get the position of a text object. + * + * \param text the TTF_Text to query. + * \param x a pointer filled in with the x offset of the upper left corner of + * this text in pixels, may be NULL. + * \param y a pointer filled in with the y offset of the upper left corner of + * this text in pixels, may be NULL. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetTextPosition + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextPosition(TTF_Text *text, int *x, int *y); + +/** + * Set whether wrapping is enabled on a text object. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param wrap_width the maximum width in pixels, 0 to wrap on newline + * characters. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetTextWrapWidth + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextWrapWidth(TTF_Text *text, int wrap_width); + +/** + * Get whether wrapping is enabled on a text object. + * + * \param text the TTF_Text to query. + * \param wrap_width a pointer filled in with the maximum width in pixels or 0 + * if the text is being wrapped on newline characters. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetTextWrapWidth + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextWrapWidth(TTF_Text *text, int *wrap_width); + +/** + * Set whether whitespace should be visible when wrapping a text object. + * + * If the whitespace is visible, it will take up space for purposes of + * alignment and wrapping. This is good for editing, but looks better when + * centered or aligned if whitespace around line wrapping is hidden. This + * defaults false. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param visible true to show whitespace when wrapping text, false to hide + * it. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_TextWrapWhitespaceVisible + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextWrapWhitespaceVisible(TTF_Text *text, bool visible); + +/** + * Return whether whitespace is shown when wrapping a text object. + * + * \param text the TTF_Text to query. + * \returns true if whitespace is shown when wrapping text, or false + * otherwise. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_SetTextWrapWhitespaceVisible + */ +extern SDL_DECLSPEC bool SDLCALL TTF_TextWrapWhitespaceVisible(TTF_Text *text); + +/** + * Set the UTF-8 text used by a text object. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param string the UTF-8 text to use, may be NULL. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_AppendTextString + * \sa TTF_DeleteTextString + * \sa TTF_InsertTextString + */ +extern SDL_DECLSPEC bool SDLCALL TTF_SetTextString(TTF_Text *text, const char *string, size_t length); + +/** + * Insert UTF-8 text into a text object. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param offset the offset, in bytes, from the beginning of the string if >= + * 0, the offset from the end of the string if < 0. Note that + * this does not do UTF-8 validation, so you should only insert + * at UTF-8 sequence boundaries. + * \param string the UTF-8 text to insert. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_AppendTextString + * \sa TTF_DeleteTextString + * \sa TTF_SetTextString + */ +extern SDL_DECLSPEC bool SDLCALL TTF_InsertTextString(TTF_Text *text, int offset, const char *string, size_t length); + +/** + * Append UTF-8 text to a text object. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param string the UTF-8 text to insert. + * \param length the length of the text, in bytes, or 0 for null terminated + * text. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_DeleteTextString + * \sa TTF_InsertTextString + * \sa TTF_SetTextString + */ +extern SDL_DECLSPEC bool SDLCALL TTF_AppendTextString(TTF_Text *text, const char *string, size_t length); + +/** + * Delete UTF-8 text from a text object. + * + * This function may cause the internal text representation to be rebuilt. + * + * \param text the TTF_Text to modify. + * \param offset the offset, in bytes, from the beginning of the string if >= + * 0, the offset from the end of the string if < 0. Note that + * this does not do UTF-8 validation, so you should only delete + * at UTF-8 sequence boundaries. + * \param length the length of text to delete, in bytes, or -1 for the + * remainder of the string. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_AppendTextString + * \sa TTF_InsertTextString + * \sa TTF_SetTextString + */ +extern SDL_DECLSPEC bool SDLCALL TTF_DeleteTextString(TTF_Text *text, int offset, int length); + +/** + * Get the size of a text object. + * + * The size of the text may change when the font or font style and size + * change. + * + * \param text the TTF_Text to query. + * \param w a pointer filled in with the width of the text, in pixels, may be + * NULL. + * \param h a pointer filled in with the height of the text, in pixels, may be + * NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSize(TTF_Text *text, int *w, int *h); + +/** + * Flags for TTF_SubString + * + * \since This datatype is available since SDL_ttf 3.0.0. + * + * \sa TTF_SubString + */ +typedef Uint32 TTF_SubStringFlags; + +#define TTF_SUBSTRING_DIRECTION_MASK 0x000000FF /**< The mask for the flow direction for this substring */ +#define TTF_SUBSTRING_TEXT_START 0x00000100 /**< This substring contains the beginning of the text */ +#define TTF_SUBSTRING_LINE_START 0x00000200 /**< This substring contains the beginning of line `line_index` */ +#define TTF_SUBSTRING_LINE_END 0x00000400 /**< This substring contains the end of line `line_index` */ +#define TTF_SUBSTRING_TEXT_END 0x00000800 /**< This substring contains the end of the text */ + +/** + * The representation of a substring within text. + * + * \since This struct is available since SDL_ttf 3.0.0. + * + * \sa TTF_GetNextTextSubString + * \sa TTF_GetPreviousTextSubString + * \sa TTF_GetTextSubString + * \sa TTF_GetTextSubStringForLine + * \sa TTF_GetTextSubStringForPoint + * \sa TTF_GetTextSubStringsForRange + */ +typedef struct TTF_SubString +{ + TTF_SubStringFlags flags; /**< The flags for this substring */ + int offset; /**< The byte offset from the beginning of the text */ + int length; /**< The byte length starting at the offset */ + int line_index; /**< The index of the line that contains this substring */ + int cluster_index; /**< The internal cluster index, used for quickly iterating */ + SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */ +} TTF_SubString; + +/** + * Get the substring of a text object that surrounds a text offset. + * + * If `offset` is less than 0, this will return a zero length substring at the + * beginning of the text with the TTF_SUBSTRING_TEXT_START flag set. If + * `offset` is greater than or equal to the length of the text string, this + * will return a zero length substring at the end of the text with the + * TTF_SUBSTRING_TEXT_END flag set. + * + * \param text the TTF_Text to query. + * \param offset a byte offset into the text string. + * \param substring a pointer filled in with the substring containing the + * offset. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring); + +/** + * Get the substring of a text object that contains the given line. + * + * If `line` is less than 0, this will return a zero length substring at the + * beginning of the text with the TTF_SUBSTRING_TEXT_START flag set. If `line` + * is greater than or equal to `text->num_lines` this will return a zero + * length substring at the end of the text with the TTF_SUBSTRING_TEXT_END + * flag set. + * + * \param text the TTF_Text to query. + * \param line a zero-based line index, in the range [0 .. text->num_lines-1]. + * \param substring a pointer filled in with the substring containing the + * offset. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substring); + +/** + * Get the substrings of a text object that contain a range of text. + * + * \param text the TTF_Text to query. + * \param offset a byte offset into the text string. + * \param length the length of the range being queried, in bytes, or -1 for + * the remainder of the string. + * \param count a pointer filled in with the number of substrings returned, + * may be NULL. + * \returns a NULL terminated array of substring pointers or NULL on failure; + * call SDL_GetError() for more information. This is a single + * allocation that should be freed with SDL_free() when it is no + * longer needed. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC TTF_SubString ** SDLCALL TTF_GetTextSubStringsForRange(TTF_Text *text, int offset, int length, int *count); + +/** + * Get the portion of a text string that is closest to a point. + * + * This will return the closest substring of text to the given point. + * + * \param text the TTF_Text to query. + * \param x the x coordinate relative to the left side of the text, may be + * outside the bounds of the text area. + * \param y the y coordinate relative to the top side of the text, may be + * outside the bounds of the text area. + * \param substring a pointer filled in with the closest substring of text to + * the given point. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *substring); + +/** + * Get the previous substring in a text object + * + * If called at the start of the text, this will return a zero length + * substring with the TTF_SUBSTRING_TEXT_START flag set. + * + * \param text the TTF_Text to query. + * \param substring the TTF_SubString to query. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetPreviousTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *previous); + +/** + * Get the next substring in a text object + * + * If called at the end of the text, this will return a zero length substring + * with the TTF_SUBSTRING_TEXT_END flag set. + * + * \param text the TTF_Text to query. + * \param substring the TTF_SubString to query. + * \param next a pointer filled in with the next substring. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_GetNextTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *next); + +/** + * Update the layout of a text object. + * + * This is automatically done when the layout is requested or the text is + * rendered, but you can call this if you need more control over the timing of + * when the layout and text engine representation are updated. + * + * \param text the TTF_Text to update. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC bool SDLCALL TTF_UpdateText(TTF_Text *text); + +/** + * Destroy a text object created by a text engine. + * + * \param text the text to destroy. + * + * \threadsafety This function should be called on the thread that created the + * text. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_CreateText + */ +extern SDL_DECLSPEC void SDLCALL TTF_DestroyText(TTF_Text *text); + +/** + * Dispose of a previously-created font. + * + * Call this when done with a font. This function will free any resources + * associated with it. It is safe to call this function on NULL, for example + * on the result of a failed call to TTF_OpenFont(). + * + * The font is not valid after being passed to this function. String pointers + * from functions that return information on this font, such as + * TTF_GetFontFamilyName() and TTF_GetFontStyleName(), are no longer valid + * after this call, as well. + * + * \param font the font to dispose of. + * + * \threadsafety This function should not be called while any other thread is + * using the font. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_OpenFont + * \sa TTF_OpenFontIO + */ +extern SDL_DECLSPEC void SDLCALL TTF_CloseFont(TTF_Font *font); + +/** + * Deinitialize SDL_ttf. + * + * You must call this when done with the library, to free internal resources. + * It is safe to call this when the library isn't initialized, as it will just + * return immediately. + * + * Once you have as many quit calls as you have had successful calls to + * TTF_Init, the library will actually deinitialize. + * + * Please note that this does not automatically close any fonts that are still + * open at the time of deinitialization, and it is possibly not safe to close + * them afterwards, as parts of the library will no longer be initialized to + * deal with it. A well-written program should call TTF_CloseFont() on any + * open fonts before calling this function! + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + */ +extern SDL_DECLSPEC void SDLCALL TTF_Quit(void); + +/** + * Check if SDL_ttf is initialized. + * + * This reports the number of times the library has been initialized by a call + * to TTF_Init(), without a paired deinitialization request from TTF_Quit(). + * + * In short: if it's greater than zero, the library is currently initialized + * and ready to work. If zero, it is not initialized. + * + * Despite the return value being a signed integer, this function should not + * return a negative number. + * + * \returns the current number of initialization calls, that need to + * eventually be paired with this many calls to TTF_Quit(). + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_ttf 3.0.0. + * + * \sa TTF_Init + * \sa TTF_Quit + */ +extern SDL_DECLSPEC int SDLCALL TTF_WasInit(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_TTF_H_ */ + diff --git a/vendor/sdl3/ttf/sdl3_textengine.odin b/vendor/sdl3/ttf/sdl3_textengine.odin new file mode 100644 index 000000000..00bf881dd --- /dev/null +++ b/vendor/sdl3/ttf/sdl3_textengine.odin @@ -0,0 +1,62 @@ +package sdl3_ttf + +import "core:c" +import SDL "vendor:sdl3" + +DrawCommand :: enum c.int { + NOOP, + FILL, + COPY, +} + +FillOperation :: struct { + cmd: DrawCommand, + rect: SDL.Rect, +} + +CopyOperation :: struct { + cmd: DrawCommand, + text_offset: c.int, + glyph_font: ^Font, + glyph_index: u32, + src: SDL.Rect, + dst: SDL.Rect, + reserved: rawptr, +} + +DrawOperation :: struct #raw_union { + cmd: DrawCommand, + fill: FillOperation, + copy: CopyOperation, +} + +TextLayout :: struct {} + +TextData :: struct { + font: ^Font, + color: SDL.FColor, + needs_layout_update: bool, + layout: ^TextLayout, + x, y: c.int, + w, h: c.int, + num_ops: c.int, + ops: [^]DrawOperation `fmt:"v,num_ops"`, + num_clusters: c.int, + clusters: [^]SubString `fmt:"v,num_clusters"`, + props: SDL.PropertiesID, + needs_engine_update: bool, + engine: ^TextEngine, + engine_text: rawptr, +} + +TextEngine :: struct { + version: u32, + userdata: rawptr, + CreateText: proc "c" (userdata: rawptr, text: ^Text) -> bool, + DestroyText: proc "c" (userdata: rawptr, Textext: ^Text), +} + +#assert( + (size_of(TextEngine) == 16 && size_of(rawptr) == 4) || + (size_of(TextEngine) == 32 && size_of(rawptr) == 8), +) diff --git a/vendor/sdl3/ttf/sdl3_ttf.odin b/vendor/sdl3/ttf/sdl3_ttf.odin new file mode 100644 index 000000000..9b46143e8 --- /dev/null +++ b/vendor/sdl3/ttf/sdl3_ttf.odin @@ -0,0 +1,279 @@ +package sdl3_ttf + +import "core:c" +import SDL "vendor:sdl3" + +when ODIN_OS == .Windows { + foreign import lib "SDL3_ttf.lib" +} else { + foreign import lib "system:SDL3_ttf" +} + + +PROP_FONT_CREATE_FILENAME_STRING :: "SDL_ttf.font.create.filename" +PROP_FONT_CREATE_IOSTREAM_POINTER :: "SDL_ttf.font.create.iostream" +PROP_FONT_CREATE_IOSTREAM_OFFSET_NUMBER :: "SDL_ttf.font.create.iostream.offset" +PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN :: "SDL_ttf.font.create.iostream.autoclose" +PROP_FONT_CREATE_SIZE_FLOAT :: "SDL_ttf.font.create.size" +PROP_FONT_CREATE_FACE_NUMBER :: "SDL_ttf.font.create.face" +PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER :: "SDL_ttf.font.create.hdpi" +PROP_FONT_CREATE_VERTICAL_DPI_NUMBER :: "SDL_ttf.font.create.vdpi" +PROP_FONT_CREATE_EXISTING_FONT :: "SDL_ttf.font.create.existing_font" + +FONT_WEIGHT_THIN :: 100 /**< Thin (100) named font weight value */ +FONT_WEIGHT_EXTRA_LIGHT :: 200 /**< ExtraLight (200) named font weight value */ +FONT_WEIGHT_LIGHT :: 300 /**< Light (300) named font weight value */ +FONT_WEIGHT_NORMAL :: 400 /**< Normal (400) named font weight value */ +FONT_WEIGHT_MEDIUM :: 500 /**< Medium (500) named font weight value */ +FONT_WEIGHT_SEMI_BOLD :: 600 /**< SemiBold (600) named font weight value */ +FONT_WEIGHT_BOLD :: 700 /**< Bold (700) named font weight value */ +FONT_WEIGHT_EXTRA_BOLD :: 800 /**< ExtraBold (800) named font weight value */ +FONT_WEIGHT_BLACK :: 900 /**< Black (900) named font weight value */ +FONT_WEIGHT_EXTRA_BLACK :: 950 /**< ExtraBlack (950) named font weight value */ + +PROP_RENDERER_TEXT_ENGINE_RENDERER :: "SDL_ttf.renderer_text_engine.create.renderer" +PROP_RENDERER_TEXT_ENGINE_ATLAS_TEXTURE_SIZE :: "SDL_ttf.renderer_text_engine.create.atlas_texture_size" + +PROP_GPU_TEXT_ENGINE_DEVICE :: "SDL_ttf.gpu_text_engine.create.device" +PROP_GPU_TEXT_ENGINE_ATLAS_TEXTURE_SIZE :: "SDL_ttf.gpu_text_engine.create.atlas_texture_size" + +MAJOR_VERSION :: 3 +MINOR_VERSION :: 2 +PATCHLEVEL :: 2 + +Font :: struct {} + +Text :: struct { + text: [^]u8, + num_lines: c.int, + refcount: c.int, + internal: ^TextData, +} + +FontStyle :: enum u32 { + NORMAL, + BOLD, + ITALIC, + UNDERLINE, + STRIKETHROUGH, +} + +FontStyleFlags :: distinct bit_set[FontStyle; u32] + +// NOTE: This is called TTF_HintingFlags but its not a bit_set so +// the "flags" doesnt really make sense, its just the hinting. +Hinting :: enum c.int { + INVALID = -1, + NORMAL, + LIGHT, + MONO, + NONE, + LIGHT_SUBPIXEL, +} + +HorizontalAlignment :: enum c.int { + INVALID = -1, + LEFT, + CENTER, + RIGHT, +} + +Direction :: enum c.int { + INVALID, + LTR = 4, + RTL, + TTB, + BTT, +} + +ImageType :: enum c.int { + INVALID, + ALPHA, + COLOR, + SDF, +} + +GPUAtlasDrawSequence :: struct { + atlas_texture: ^SDL.GPUTexture, + xy, uv: [^]SDL.FPoint `fmt:"v,num_vertices"`, + num_vertices: c.int, + indices: [^]c.int `fmt:"v,num_indices"`, + num_indices: c.int, + image_type: ImageType, + next: ^GPUAtlasDrawSequence, +} + +GPUTextEngineWinding :: enum c.int { + INVALID = -1, + CLOCKWISE = 0, + COUNTER_CLOCKWISE = +1, +} + +SubStringFlags :: bit_field u32 { + direction: u8 | 8, + text_start: bool | 1, + line_start: bool | 1, + line_end: bool | 1, + text_end: bool | 1, +} + +SubString :: struct { + flags: SubStringFlags, + offset, length: c.int, + line_index, cluster_index: c.int, + rect: SDL.Rect, +} + +@(default_calling_convention="c", link_prefix="TTF_", require_results) +foreign lib { + Version :: proc() -> c.int --- + GetFreeTypeVersion :: proc(major, minor, patch: ^c.int) --- + GetHarfBuzzVersion :: proc(major, minor, patch: ^c.int) --- + + Init :: proc() -> bool --- + + OpenFont :: proc(file: cstring, ptsize: f32) -> ^Font --- + OpenFontIO :: proc(src: ^SDL.IOStream, closeio: bool, ptsize: f32) -> ^Font --- + OpenFontWithProperties :: proc(props: SDL.PropertiesID) -> ^Font --- + + CopyFont :: proc(existing_font: ^Font) -> ^Font --- + + GetFontProperties :: proc(font: ^Font) -> SDL.PropertiesID --- + GetFontGeneration :: proc(font: ^Font) -> u32 --- + + AddFallbackFont :: proc(font: ^Font, fallback: ^Font) -> bool --- + RemoveFallbackFont :: proc(font: ^Font, fallback: ^Font) --- + ClearFallbackFonts :: proc(font: ^Font) --- + + SetFontSize :: proc(font: ^Font, ptsize: f32) -> bool --- + SetFontSizeDPI :: proc(font: ^Font, ptsize: f32, hdpi: c.int, vdpi: c.int) -> bool --- + GetFontSize :: proc(font: ^Font) -> f32 --- + GetFontDPI :: proc(font: ^Font, hdpi: ^c.int, vdpi: ^c.int) -> bool --- + + SetFontStyle :: proc(font: ^Font, style: FontStyleFlags) --- + GetFontStyle :: proc(#by_ptr font: Font) -> FontStyleFlags --- + + SetFontOutline :: proc(font: ^Font, outline: c.int) -> bool --- + GetFontOutline :: proc(#by_ptr font: Font) -> c.int --- + + SetFontHinting :: proc(font: ^Font, hinting: Hinting) --- + GetFontHinting :: proc(#by_ptr font: Font) -> Hinting --- + + GetNumFontFaces :: proc(font: ^Font) -> c.int --- + + SetFontSDF :: proc(font: ^Font, enabled: bool) -> bool --- + GetFontSDF :: proc(#by_ptr font: Font) -> bool --- + + GetFontWeight :: proc(#by_ptr font: Font) -> c.int --- + + SetFontWrapAlignment :: proc(font: ^Font, align: HorizontalAlignment) --- + GetFontWrapAlignment :: proc(#by_ptr font: Font) -> HorizontalAlignment --- + + GetFontHeight :: proc(#by_ptr font: Font) -> c.int --- + GetFontAscent :: proc(#by_ptr font: Font) -> c.int --- + GetFontDescent :: proc(#by_ptr font: Font) -> c.int --- + + SetFontLineSkip :: proc(font: ^Font, lineskip: c.int) --- + GetFontLineSkip :: proc(#by_ptr font: Font) -> c.int --- + + SetFontKerning :: proc(font: ^Font, enabled: bool) --- + GetFontKerning :: proc(#by_ptr font: Font) -> bool --- + + FontIsFixedWidth :: proc(#by_ptr font: Font) -> bool --- + FontIsScalable :: proc(#by_ptr font: Font) -> bool --- + + GetFontFamilyName :: proc(#by_ptr font: Font) -> cstring --- + GetFontStyleName :: proc(#by_ptr font: Font) -> cstring --- + + SetFontDirection :: proc(font: ^Font, direction: Direction) -> bool --- + GetFontDirection :: proc(#by_ptr font: Font) -> Direction --- + + StringToTag :: proc(string: cstring) -> u32 --- + TagToString :: proc(tag: u32, string: [^]c.char, size: c.size_t) --- + + SetFontScript :: proc(font: ^Font, script: u32) -> bool --- + GetFontScript :: proc(font: ^Font) -> u32 --- + + SetFontLanguage :: proc(font: ^Font, language_bcp47: cstring) -> bool --- + + GetGlyphScript :: proc(ch: u32) -> u32 --- + FontHasGlyph :: proc(font: ^Font, ch: u32) -> bool --- + GetGlyphImage :: proc(font: ^Font, ch: u32, image_type: ^ImageType) -> ^SDL.Surface --- + GetGlyphImageForIndex :: proc(font: ^Font, glyph_index: u32, image_type: ^ImageType) -> ^SDL.Surface --- + GetGlyphMetrics :: proc(font: ^Font, ch: u32, minx, maxx, miny, maxy, advance: ^c.int) -> bool --- + GetGlyphKerning :: proc(font: ^Font, previous_ch: u32, ch: u32, kerning: ^c.int) -> bool --- + + GetStringSize :: proc(font: ^Font, text: cstring, length: c.size_t, w, h: ^c.int) -> bool --- + GetStringSizeWrapped :: proc(font: ^Font, text: cstring, length: c.size_t, wrap_width: c.int, w, h: ^c.int) -> bool --- + MeasureString :: proc(font: ^Font, text: cstring, length: c.size_t, max_width: c.int, measured_width: ^c.int, measured_length: ^c.size_t) -> bool --- + + RenderText_Solid :: proc(font: ^Font, text: cstring, length: c.size_t, fg: SDL.Color) -> ^SDL.Surface --- + RenderText_Solid_Wrapped :: proc(font: ^Font, text: cstring, length: c.size_t, fg: SDL.Color, wrap_Length: c.int) -> ^SDL.Surface --- + RenderGylph_Solid :: proc(font: ^Font, ch: u32, fg: SDL.Color) -> ^SDL.Surface --- + RenderText_Shaded :: proc(font: ^Font, text: cstring, length: c.size_t, fg, bg: SDL.Color) -> ^SDL.Surface --- + RenderText_Shaded_Wrapped :: proc(font: ^Font, text: cstring, length: c.size_t, fg, bg: SDL.Color, wrap_width: c.int) -> ^SDL.Surface --- + RenderGlyph_Shaded :: proc(font: ^Font, ch: u32, fg, bg: SDL.Color) -> ^SDL.Surface --- + RenderText_Blended :: proc(font: ^Font, text: cstring, length: c.size_t, fg: SDL.Color) -> ^SDL.Surface --- + RnederText_Blended_Wrapped :: proc(font: ^Font, text: cstring, length: c.size_t, fg: SDL.Color, wrap_width: c.int) -> ^SDL.Surface --- + RenderGlyph_Blended :: proc(font: ^Font, ch: u32, fg: SDL.Color) -> ^SDL.Surface --- + RenderText_LCD :: proc(font: ^Font, text: cstring, length: c.size_t, fg, bg: SDL.Color) -> ^SDL.Surface --- + RenderText_LCD_Wrapped :: proc(font: ^Font, text: cstring, length: c.size_t, fg, bg: SDL.Color, wrap_width: c.int) -> ^SDL.Surface --- + RenderGlyph_LCD :: proc(font: ^Font, ch: u32, fg, bg: SDL.Color) -> ^SDL.Surface --- + + CreateSurfaceTextEngine :: proc() -> ^TextEngine --- + DrawSurfaceText :: proc(text: ^Text, x, y: c.int, surface: ^SDL.Surface) -> bool --- + DestroySurfaceTextEngine :: proc(engine: ^TextEngine) --- + + CreateRendererTextEngine :: proc(renderer: ^SDL.Renderer) -> ^TextEngine --- + CreateRendererTextEngineWithProperties :: proc(props: SDL.PropertiesID) -> ^TextEngine --- + DrawRendererText :: proc(text: ^Text, x, y: f32) -> bool --- + DestroyRendererTextEngine :: proc(engine: ^TextEngine) --- + + CreateGPUTextEngine :: proc(device: ^SDL.GPUDevice) -> ^TextEngine --- + CreateGPUTextEngineWithProperties :: proc(props: SDL.PropertiesID) -> ^TextEngine --- + GetGPUTextDrawData :: proc(text: ^Text) -> ^GPUAtlasDrawSequence --- + DestroyGPUTextEngine :: proc(engine: ^TextEngine) --- + SetGPUTextEngineWinding :: proc(engine: ^TextEngine, winding: GPUTextEngineWinding) --- + GetGPUTextEngineWinding :: proc(#by_ptr engine: TextEngine) -> GPUTextEngineWinding --- + + CreateText :: proc(engine: ^TextEngine, font: ^Font, text: cstring, length: c.size_t) -> ^Text --- + GetTextProperties :: proc(text: ^Text) -> SDL.PropertiesID --- + SetTextEngine :: proc(text: ^Text, engine: ^TextEngine) -> bool --- + GetTextEngine :: proc(text: ^Text) -> ^TextEngine --- + SetTextFont :: proc(text: ^Text, font: ^Font) -> bool --- + GetTextFont :: proc(text: ^Text) -> ^Font --- + SetTextDirection :: proc(text: ^Text, direction: Direction) -> bool --- + GetTextDirection :: proc(text: ^Text) -> Direction --- + SetTextScript :: proc(text: ^Text, script: u32) -> bool --- + GetTextScript :: proc(text: ^Text) -> u32 --- + SetTextColor :: proc(text: ^Text, r, g, b, a: u8) -> bool --- + SetTextColorFloat :: proc(text: ^Text, r, g, b, a: f32) -> bool --- + GetTextColor :: proc(text: ^Text, r, g, b, a: ^u8) -> bool --- + GetTextColorFloat :: proc(text: ^Text, r, g, b, a: ^f32) -> bool --- + SetTextPosition :: proc(text: ^Text, x, y: c.int) -> bool --- + GetTextPosition :: proc(text: ^Text, x, y: ^c.int) -> bool --- + SetTextWrapWidth :: proc(text: ^Text, wrap_width: c.int) -> bool --- + GetTextWrapWidth :: proc(text: ^Text, wrap_width: ^c.int) -> bool --- + SetTextWrapWhitespaceVisible :: proc(text: ^Text, visible: bool) -> bool --- + TextWrapWhitespaceVisible :: proc(text: ^Text) -> bool --- + + SetTextString :: proc(text: ^Text, string: cstring, length: c.size_t) -> bool --- + InsertTextString :: proc(text: ^Text, offset: c.int, string: cstring, length: c.size_t) -> bool --- + AppendTextString :: proc(text: ^Text, string: cstring, length: c.size_t) -> bool --- + DeleteTextString :: proc(text: ^Text, offset, length: c.int) -> bool --- + + GetTextSize :: proc(text: ^Text, w, h: ^c.int) -> bool --- + + GetTextSubString :: proc(text: ^Text, offset: c.int, substring: ^SubString) -> bool --- + GetTextSubStringForLine :: proc(text: ^Text, line: c.int, substring: ^SubString) -> bool --- + GetTextSubStringsForRange :: proc(text: ^Text, offset, length: c.int, count: ^c.int) -> [^]^SubString --- + GetTextSubStringForPoint :: proc(text: ^Text, x, y: c.int, substring: ^SubString) -> bool --- + GetPreviousTextSubString :: proc(text: ^Text, #by_ptr substring: SubString, previous: ^SubString) -> bool --- + GetNextTextSubString :: proc(text: ^Text, #by_ptr substring: SubString, next: ^SubString) -> bool --- + + UpdateText :: proc(text: ^Text) -> bool --- + DestroyText :: proc(text: ^Text) --- + CloseFont :: proc(font: ^Font) --- + Quit :: proc() --- + WasInit :: proc() -> c.int --- +} diff --git a/vendor/windows/XAudio2/x3daudio.odin b/vendor/windows/XAudio2/x3daudio.odin new file mode 100644 index 000000000..27c4dc9fa --- /dev/null +++ b/vendor/windows/XAudio2/x3daudio.odin @@ -0,0 +1,233 @@ +#+build windows + +/* NOTES: + 1. Definition of terms: + LFE: Low Frequency Effect -- always omnidirectional. + LPF: Low Pass Filter, divided into two classifications: + Direct -- Applied to the direct signal path, + used for obstruction/occlusion effects. + Reverb -- Applied to the reverb signal path, + used for occlusion effects only. + + 2. Volume level is expressed as a linear amplitude scaler: + 1.0f represents no attenuation applied to the original signal, + 0.5f denotes an attenuation of 6dB, and 0.0f results in silence. + Amplification (volume > 1.0f) is also allowed, and is not clamped. + + LPF values range from 1.0f representing all frequencies pass through, + to 0.0f which results in silence as all frequencies are filtered out. + + 3. X3DAudio uses a left-handed Cartesian coordinate system with values + on the x-axis increasing from left to right, on the y-axis from + bottom to top, and on the z-axis from near to far. + Azimuths are measured clockwise from a given reference direction. + + Distance measurement is with respect to user-defined world units. + Applications may provide coordinates using any system of measure + as all non-normalized calculations are scale invariant, with such + operations natively occurring in user-defined world unit space. + Metric constants are supplied only as a convenience. + Distance is calculated using the Euclidean norm formula. + + 4. Only real values are permissible with functions using 32-bit + float parameters -- NAN and infinite values are not accepted. + All computation occurs in 32-bit precision mode. */ + + +package windows_xaudio2 + +import "core:math" + +foreign import xa2 "system:xaudio2.lib" + +//---------------------------------------------------// +// speaker geometry configuration flags, specifies assignment of channels to speaker positions, defined as per WAVEFORMATEXTENSIBLE.dwChannelMask +SPEAKER_FLAGS :: distinct bit_set[SPEAKER_FLAG; u32] +SPEAKER_FLAG :: enum u32 { + FRONT_LEFT = 0, + FRONT_RIGHT = 1, + FRONT_CENTER = 2, + LOW_FREQUENCY = 3, + BACK_LEFT = 4, + BACK_RIGHT = 5, + FRONT_LEFT_OF_CENTER = 6, + FRONT_RIGHT_OF_CENTER = 7, + BACK_CENTER = 8, + SIDE_LEFT = 9, + SIDE_RIGHT = 10, + TOP_CENTER = 11, + TOP_FRONT_LEFT = 12, + TOP_FRONT_CENTER = 13, + TOP_FRONT_RIGHT = 14, + TOP_BACK_LEFT = 15, + TOP_BACK_CENTER = 16, + TOP_BACK_RIGHT = 17, + //RESERVED = 0x7FFC0000, // bit mask locations reserved for future use + ALL = 31, // used to specify that any possible permutation of speaker configurations +} + +// standard speaker geometry configurations, used with Initialize +SPEAKER_MONO :: SPEAKER_FLAGS{.FRONT_CENTER} +SPEAKER_STEREO :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT} +SPEAKER_2POINT1 :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .LOW_FREQUENCY} +SPEAKER_SURROUND :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .FRONT_CENTER, .BACK_CENTER} +SPEAKER_QUAD :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .BACK_LEFT, .BACK_RIGHT} +SPEAKER_4POINT1 :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .LOW_FREQUENCY, .BACK_LEFT, .BACK_RIGHT} +SPEAKER_5POINT1 :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .FRONT_CENTER, .LOW_FREQUENCY, .BACK_LEFT, .BACK_RIGHT} +SPEAKER_7POINT1 :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .FRONT_CENTER, .LOW_FREQUENCY, .BACK_LEFT, .BACK_RIGHT, .FRONT_LEFT_OF_CENTER, .FRONT_RIGHT_OF_CENTER} +SPEAKER_5POINT1_SURROUND :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .FRONT_CENTER, .LOW_FREQUENCY, .SIDE_LEFT, .SIDE_RIGHT} +SPEAKER_7POINT1_SURROUND :: SPEAKER_FLAGS{.FRONT_LEFT, .FRONT_RIGHT, .FRONT_CENTER, .LOW_FREQUENCY, .BACK_LEFT, .BACK_RIGHT, .SIDE_LEFT, .SIDE_RIGHT} + +// size of instance handle in bytes +HANDLE_BYTESIZE :: 20 + +// speed of sound in meters per second for dry air at approximately 20C, used with Initialize +SPEED_OF_SOUND :: 343.5 + +// calculation control flags, used with Calculate +CALCULATE_FLAGS :: distinct bit_set[CALCULATE_FLAG; u32] +CALCULATE_FLAG :: enum u32 { + MATRIX = 0, // enable matrix coefficient table calculation + DELAY = 1, // enable delay time array calculation (stereo final mix only) + LPF_DIRECT = 2, // enable LPF direct-path coefficient calculation + LPF_REVERB = 3, // enable LPF reverb-path coefficient calculation + REVERB = 4, // enable reverb send level calculation + DOPPLER = 5, // enable doppler shift factor calculation + EMITTER_ANGLE = 6, // enable emitter-to-listener interior angle calculation + + ZEROCENTER = 16, // do not position to front center speaker, signal positioned to remaining speakers instead, front center destination channel will be zero in returned matrix coefficient table, valid only for matrix calculations with final mix formats that have a front center channel + REDIRECT_TO_LFE = 17, // apply equal mix of all source channels to LFE destination channel, valid only for matrix calculations with sources that have no LFE channel and final mix formats that have an LFE channel +} + +//-----------------------------------------------------// +VECTOR :: [3]f32 // float 3D vector + +// instance handle of precalculated constants +HANDLE :: distinct [HANDLE_BYTESIZE]byte + +// Distance curve point: +// Defines a DSP setting at a given normalized distance. +DISTANCE_CURVE_POINT :: struct #packed { + Distance: f32, // normalized distance, must be within [0.0f, 1.0f] + DSPSetting: f32, // DSP setting +} + +// Distance curve: +// A piecewise curve made up of linear segments used to define DSP behaviour with respect to normalized distance. +// +// Note that curve point distances are normalized within [0.0f, 1.0f]. +// EMITTER.CurveDistanceScaler must be used to scale the normalized distances to user-defined world units. +// For distances beyond CurveDistanceScaler * 1.0f, pPoints[PointCount-1].DSPSetting is used as the DSP setting. +// +// All distance curve spans must be such that: +// pPoints[k-1].DSPSetting + ((pPoints[k].DSPSetting-pPoints[k-1].DSPSetting) / (pPoints[k].Distance-pPoints[k-1].Distance)) * (pPoints[k].Distance-pPoints[k-1].Distance) != NAN or infinite values +// For all points in the distance curve where 1 <= k < PointCount. +DISTANCE_CURVE :: struct #packed { + pPoints: [^]DISTANCE_CURVE_POINT `fmt:"v,PointCount"`, // distance curve point array, must have at least PointCount elements with no duplicates and be sorted in ascending order with respect to Distance + PointCount: u32, // number of distance curve points, must be >= 2 as all distance curves must have at least two endpoints, defining DSP settings at 0.0f and 1.0f normalized distance +} +Default_LinearCurvePoints := [2]DISTANCE_CURVE_POINT{{0.0, 1.0}, {1.0, 0.0}} +Default_LinearCurve := DISTANCE_CURVE{&Default_LinearCurvePoints[0], 2} + +CONE :: struct #packed { + InnerAngle: f32, // inner cone angle in radians, must be within [0.0f, TAU] + OuterAngle: f32, // outer cone angle in radians, must be within [InnerAngle, TAU] + + InnerVolume: f32, // volume level scaler on/within inner cone, used only for matrix calculations, must be within [0.0f, 2.0f] when used + OuterVolume: f32, // volume level scaler on/beyond outer cone, used only for matrix calculations, must be within [0.0f, 2.0f] when used + InnerLPF: f32, // LPF (both direct and reverb paths) coefficient subtrahend on/within inner cone, used only for LPF (both direct and reverb paths) calculations, must be within [0.0f, 1.0f] when used + OuterLPF: f32, // LPF (both direct and reverb paths) coefficient subtrahend on/beyond outer cone, used only for LPF (both direct and reverb paths) calculations, must be within [0.0f, 1.0f] when used + InnerReverb: f32, // reverb send level scaler on/within inner cone, used only for reverb calculations, must be within [0.0f, 2.0f] when used + OuterReverb: f32, // reverb send level scaler on/beyond outer cone, used only for reverb calculations, must be within [0.0f, 2.0f] when used +} +Default_DirectionalCone := CONE{math.PI / 2, math.PI, 1.0, 0.708, 0.0, 0.25, 0.708, 1.0} + +// Listener: +// Defines a point of 3D audio reception. +// +// The cone is directed by the listener's front orientation. +LISTENER :: struct #packed { + OrientFront: VECTOR, // orientation of front direction, used only for matrix and delay calculations or listeners with cones for matrix, LPF (both direct and reverb paths), and reverb calculations, must be normalized when used + OrientTop: VECTOR, // orientation of top direction, used only for matrix and delay calculations, must be orthonormal with OrientFront when used + + Position: VECTOR, // position in user-defined world units, does not affect Velocity + Velocity: VECTOR, // velocity vector in user-defined world units/second, used only for doppler calculations, does not affect Position + + pCone: ^CONE, // sound cone, used only for matrix, LPF (both direct and reverb paths), and reverb calculations, NULL specifies omnidirectionality +} + +// Emitter: +// Defines a 3D audio source, divided into two classifications: +// +// Single-point -- For use with single-channel sounds. +// Positioned at the emitter base, i.e. the channel radius and azimuth are ignored if the number of channels == 1. +// +// May be omnidirectional or directional using a cone. +// The cone originates from the emitter base position, and is directed by the emitter's front orientation. +// +// Multi-point -- For use with multi-channel sounds. +// Each non-LFE channel is positioned using an azimuth along the channel radius with respect to the front orientation vector in the plane orthogonal to the top orientation vector. +// An azimuth of TAU specifies a channel is an LFE. Such channels are positioned at the emitter base and are calculated with respect to pLFECurve only, never pVolumeCurve. +// +// Multi-point emitters are always omnidirectional, i.e. the cone is ignored if the number of channels > 1. +// +// Note that many properties are shared among all channel points, locking certain behaviour with respect to the emitter base position. +// For example, doppler shift is always calculated with respect to the emitter base position and so is constant for all its channel points. +// Distance curve calculations are also with respect to the emitter base position, with the curves being calculated independently of each other. +// For instance, volume and LFE calculations do not affect one another. +EMITTER :: struct #packed { + pCone: ^CONE, // sound cone, used only with single-channel emitters for matrix, LPF (both direct and reverb paths), and reverb calculations, NULL specifies omnidirectionality + + OrientFront: VECTOR, // orientation of front direction, used only for emitter angle calculations or with multi-channel emitters for matrix calculations or single-channel emitters with cones for matrix, LPF (both direct and reverb paths), and reverb calculations, must be normalized when used + OrientTop: VECTOR, // orientation of top direction, used only with multi-channel emitters for matrix calculations, must be orthonormal with OrientFront when used + + Position: VECTOR, // position in user-defined world units, does not affect Velocity + Velocity: VECTOR, // velocity vector in user-defined world units/second, used only for doppler calculations, does not affect Position + + InnerRadius: f32, // inner radius, must be within [0.0f, max(f32)] + InnerRadiusAngle: f32, // inner radius angle, must be within [0.0f, PI/4.0) + + ChannelCount: u32, // number of sound channels, must be > 0 + ChannelRadius: f32, // channel radius, used only with multi-channel emitters for matrix calculations, must be >= 0.0f when used + pChannelAzimuths: [^]f32 `fmt:"v,ChannelCount"`, // channel azimuth array, used only with multi-channel emitters for matrix calculations, contains positions of each channel expressed in radians along the channel radius with respect to the front orientation vector in the plane orthogonal to the top orientation vector, or TAU to specify an LFE channel, must have at least ChannelCount elements, all within [0.0f, TAU] when used + + pVolumeCurve: ^DISTANCE_CURVE, // volume level distance curve, used only for matrix calculations, NULL specifies a default curve that conforms to the inverse square law, calculated in user-defined world units with distances <= CurveDistanceScaler clamped to no attenuation + pLFECurve: ^DISTANCE_CURVE, // LFE level distance curve, used only for matrix calculations, NULL specifies a default curve that conforms to the inverse square law, calculated in user-defined world units with distances <= CurveDistanceScaler clamped to no attenuation + pLPFDirectCurve: ^DISTANCE_CURVE, // LPF direct-path coefficient distance curve, used only for LPF direct-path calculations, NULL specifies the default curve: [0.0f,1.0f], [1.0f,0.75f] + pLPFReverbCurve: ^DISTANCE_CURVE, // LPF reverb-path coefficient distance curve, used only for LPF reverb-path calculations, NULL specifies the default curve: [0.0f,0.75f], [1.0f,0.75f] + pReverbCurve: ^DISTANCE_CURVE, // reverb send level distance curve, used only for reverb calculations, NULL specifies the default curve: [0.0f,1.0f], [1.0f,0.0f] + + CurveDistanceScaler: f32, // curve distance scaler, used to scale normalized distance curves to user-defined world units and/or exaggerate their effect, used only for matrix, LPF (both direct and reverb paths), and reverb calculations, must be within [min(f32), max(f32)] when used + DopplerScaler: f32, // doppler shift scaler, used to exaggerate doppler shift effect, used only for doppler calculations, must be within [0.0f, max(f32)] when used +} + +// DSP settings: +// Receives results from a call to Calculate to be sent to the low-level audio rendering API for 3D signal processing. +// +// The user is responsible for allocating the matrix coefficient table, delay time array, and initializing the channel counts when used. +DSP_SETTINGS :: struct #packed { + pMatrixCoefficients: [^]f32, // [inout] matrix coefficient table, receives an array representing the volume level used to send from source channel S to destination channel D, stored as pMatrixCoefficients[SrcChannelCount * D + S], must have at least SrcChannelCount*DstChannelCount elements + pDelayTimes: [^]f32, // [inout] delay time array, receives delays for each destination channel in milliseconds, must have at least DstChannelCount elements (stereo final mix only) + SrcChannelCount: u32, // [in] number of source channels, must equal number of channels in respective emitter + DstChannelCount: u32, // [in] number of destination channels, must equal number of channels of the final mix + + LPFDirectCoefficient: f32, // [out] LPF direct-path coefficient + LPFReverbCoefficient: f32, // [out] LPF reverb-path coefficient + ReverbLevel: f32, // [out] reverb send level + DopplerFactor: f32, // [out] doppler shift factor, scales resampler ratio for doppler shift effect, where the effective frequency = DopplerFactor * original frequency + EmitterToListenerAngle: f32, // [out] emitter-to-listener interior angle, expressed in radians with respect to the emitter's front orientation + + EmitterToListenerDistance: f32, // [out] distance in user-defined world units from the emitter base to listener position, always calculated + EmitterVelocityComponent: f32, // [out] component of emitter velocity vector projected onto emitter->listener vector in user-defined world units/second, calculated only for doppler + ListenerVelocityComponent: f32, // [out] component of listener velocity vector projected onto emitter->listener vector in user-defined world units/second, calculated only for doppler +} + +//-------------------------------------------------------// +@(default_calling_convention="cdecl", link_prefix="X3DAudio") +foreign xa2 { + // initializes instance handle + Initialize :: proc(SpeakerChannelMask: SPEAKER_FLAGS, SpeedOfSound: f32, Instance: HANDLE) -> HRESULT --- + + // calculates DSP settings with respect to 3D parameters + Calculate :: proc(Instance: HANDLE, #by_ptr pListener: LISTENER, #by_ptr pEmitter: EMITTER, Flags: CALCULATE_FLAGS, pDSPSettings: ^DSP_SETTINGS) --- +} diff --git a/vendor/windows/XAudio2/xapo.odin b/vendor/windows/XAudio2/xapo.odin new file mode 100644 index 000000000..54fb420ca --- /dev/null +++ b/vendor/windows/XAudio2/xapo.odin @@ -0,0 +1,377 @@ +#+build windows + +/* NOTES: + 1. Definition of terms: + DSP: Digital Signal Processing. + + CBR: Constant BitRate -- DSP that consumes a constant number of + input samples to produce an output sample. + For example, a 22kHz to 44kHz resampler is CBR DSP. + Even though the number of input to output samples differ, + the ratio between input to output rate remains constant. + All user-defined XAPOs are assumed to be CBR as + XAudio2 only allows CBR DSP to be added to an effect chain. + + XAPO: Cross-platform Audio Processing Object -- + a thin wrapper that manages DSP code, allowing it + to be easily plugged into an XAudio2 effect chain. + + Frame: A block of samples, one per channel, + to be played simultaneously. + E.g. a mono stream has one sample per frame. + + In-Place: Processing such that the input buffer equals the + output buffer (i.e. input data modified directly). + This form of processing is generally more efficient + than using separate memory for input and output. + However, an XAPO may not perform format conversion + when processing in-place. + + 2. XAPO member variables are divided into three classifications: + Immutable: Set once via IXAPO.Initialize and remain + constant during the lifespan of the XAPO. + + Locked: May change before the XAPO is locked via + IXAPO.LockForProcess but remain constant + until IXAPO.UnlockForProcess is called. + + Dynamic: May change from one processing pass to the next, + usually via IXAPOParameters.SetParameters. + XAPOs should assign reasonable defaults to their dynamic + variables during IXAPO.Initialize/LockForProcess so + that calling IXAPOParameters.SetParameters is not + required before processing begins. + + When implementing an XAPO, determine the type of each variable and + initialize them in the appropriate method. Immutable variables are + generally preferable over locked which are preferable over dynamic. + That is, one should strive to minimize XAPO state changes for + best performance, maintainability, and ease of use. + + 3. To minimize glitches, the realtime audio processing thread must + not block. XAPO methods called by the realtime thread are commented + as non-blocking and therefore should not use blocking synchronization, + allocate memory, access the disk, etc. The XAPO interfaces were + designed to allow an effect implementer to move such operations + into other methods called on an application controlled thread. + + 4. Extending functionality is accomplished through the addition of new + COM interfaces. For example, if a new member is added to a parameter + structure, a new interface using the new structure should be added, + leaving the original interface unchanged. + This ensures consistent communication between future versions of + XAudio2 and various versions of XAPOs that may exist in an application. + + 5. All audio data is interleaved in XAudio2. + The default audio format for an effect chain is WAVE_FORMAT_IEEE_FLOAT. + + 6. User-defined XAPOs should assume all input and output buffers are + 16-byte aligned. + + 7. See XAPOBase.odin for an XAPO base class which provides a default + implementation for most of the interface methods defined below. */ + +package windows_xaudio2 + +import win "core:sys/windows" + +//---------------------------------------------------// + +// XAPO error codes +FORMAT_UNSUPPORTED := win.MAKE_HRESULT(win.SEVERITY.ERROR, 0x897, 0x01) // requested audio format unsupported + +// supported number of channels (samples per frame) range +XAPO_MIN_CHANNELS :: 1 +XAPO_MAX_CHANNELS :: 64 + +// supported framerate range +XAPO_MIN_FRAMERATE :: 1000 +XAPO_MAX_FRAMERATE :: 200000 + +// unicode string length, including terminator, used with XAPO_REGISTRATION_PROPERTIES +XAPO_REGISTRATION_STRING_LENGTH :: 256 + + +// XAPO property flags, used with XAPO_REGISTRATION_PROPERTIES.Flags: +XAPO_FLAGS :: distinct bit_set[XAPO_FLAG; u32] +XAPO_FLAG :: enum u32 { + // Number of channels of input and output buffers must match, applies to XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.pFormat. + CHANNELS_MUST_MATCH = 0, + + // Framerate of input and output buffers must match, applies to XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.pFormat. + FRAMERATE_MUST_MATCH = 1, + + // Bit depth of input and output buffers must match, applies to XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.pFormat. + // Container size of input and output buffers must also match if XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.pFormat is WAVEFORMATEXTENSIBLE. + BITSPERSAMPLE_MUST_MATCH = 2, + + // Number of input and output buffers must match, applies to XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS. + // Also, XAPO_REGISTRATION_PROPERTIES.MinInputBufferCount must equal XAPO_REGISTRATION_PROPERTIES.MinOutputBufferCount and XAPO_REGISTRATION_PROPERTIES.MaxInputBufferCount must equal XAPO_REGISTRATION_PROPERTIES.MaxOutputBufferCount when used. + BUFFERCOUNT_MUST_MATCH = 3, + + // XAPO must be run in-place. Use this flag only if your DSP implementation cannot process separate input and output buffers. + // If set, the following flags must also be set: + // XAPO_FLAG_CHANNELS_MUST_MATCH + // XAPO_FLAG_FRAMERATE_MUST_MATCH + // XAPO_FLAG_BITSPERSAMPLE_MUST_MATCH + // XAPO_FLAG_BUFFERCOUNT_MUST_MATCH + // XAPO_FLAG_INPLACE_SUPPORTED + // Multiple input and output buffers may be used with in-place XAPOs, though the input buffer count must equal the output buffer count. + // When multiple input/output buffers are used, the XAPO may assume input buffer [N] equals output buffer [N] for in-place processing. + INPLACE_REQUIRED = 5, + + // XAPO may be run in-place. If the XAPO is used in a chain such that the requirements for XAPO_FLAG_INPLACE_REQUIRED are met, XAudio2 will ensure the XAPO is run in-place. + // If not met, XAudio2 will still run the XAPO albeit with separate input and output buffers. + // For example, consider an effect which may be ran in stereo->5.1 mode or mono->mono mode. When set to stereo->5.1, it will be run with separate input and output buffers as format conversion is not permitted in-place. + // However, if configured to run mono->mono, the same XAPO can be run in-place. Thus the same implementation may be conveniently reused for various input/output configurations, while taking advantage of in-place processing when possible. + INPLACE_SUPPORTED = 4, +} + +//-----------------------------------------------------// + +// XAPO registration properties, describes general XAPO characteristics, used with IXAPO.GetRegistrationProperties +XAPO_REGISTRATION_PROPERTIES :: struct #packed { + clsid: win.CLSID, // COM class ID, used with CoCreate + FriendlyName: [XAPO_REGISTRATION_STRING_LENGTH]u16, // friendly name unicode string + CopyrightInfo: [XAPO_REGISTRATION_STRING_LENGTH]u16, // copyright information unicode string + MajorVersion: u32, // major version + MinorVersion: u32, // minor version + Flags: XAPO_FLAGS, // XAPO property flags, describes supported input/output configuration + MinInputBufferCount: u32, // minimum number of input buffers required for processing, can be 0 + MaxInputBufferCount: u32, // maximum number of input buffers supported for processing, must be >= MinInputBufferCount + MinOutputBufferCount: u32, // minimum number of output buffers required for processing, can be 0, must match MinInputBufferCount when XAPO_FLAG_BUFFERCOUNT_MUST_MATCH used + MaxOutputBufferCount: u32, // maximum number of output buffers supported for processing, must be >= MinOutputBufferCount, must match MaxInputBufferCount when XAPO_FLAG_BUFFERCOUNT_MUST_MATCH used +} + +// LockForProcess buffer parameters: +// Defines buffer parameters that remain constant while an XAPO is locked. +// Used with IXAPO::LockForProcess. +// For CBR XAPOs, MaxFrameCount is the only number of frames +// IXAPO::Process would have to handle for the respective buffer. +XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS :: struct #packed { + pFormat: ^WAVEFORMATEX, // buffer audio format + MaxFrameCount: u32, // maximum number of frames in respective buffer that IXAPO::Process would have to handle, irrespective of dynamic variable settings, can be 0 +} + +// Buffer flags: +// Describes assumed content of the respective buffer. +// Used with XAPO_PROCESS_BUFFER_PARAMETERS.BufferFlags. +// This meta-data can be used by an XAPO to implement optimizations that require knowledge of a buffer's content. +// For example, XAPOs that always produce silent output from silent input can check the flag on the input buffer to determine if any signal processing is necessary. +// If silent, the XAPO may simply set the flag on the output buffer to silent and return, optimizing out the work of processing silent data: XAPOs that generate silence for any reason may set the buffer's flag accordingly rather than writing out silent frames to the buffer itself. +// The flags represent what should be assumed is in the respective buffer. The flags may not reflect what is actually stored in memory. +XAPO_BUFFER_FLAGS :: enum i32 { + XAPO_BUFFER_SILENT, // silent data should be assumed, respective memory may be uninitialized + XAPO_BUFFER_VALID, // arbitrary data should be assumed (may or may not be silent frames), respective memory initialized +} + +// Process buffer parameters: +// Defines buffer parameters that may change from one +// processing pass to the next. Used with IXAPO::Process. +// +// Note the byte size of the respective buffer must be at least: +// XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount * XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.pFormat->nBlockAlign +// +// Although the audio format and maximum size of the respective +// buffer is locked (defined by XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS), +// the actual memory address of the buffer given is permitted to change +// from one processing pass to the next. +// +// For CBR XAPOs, ValidFrameCount is constant while locked and equals +// the respective XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount. +XAPO_PROCESS_BUFFER_PARAMETERS :: struct #packed { + pBuffer: rawptr, // audio data buffer, must be non-NULL + BufferFlags: XAPO_BUFFER_FLAGS, // describes assumed content of pBuffer, does not affect ValidFrameCount + ValidFrameCount: u32, // number of frames of valid data, must be within respective [0, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount], always XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount for CBR/user-defined XAPOs, does not affect BufferFlags +} + +XAPOFree :: win.CoTaskMemFree + +IXAPO_UUID_STRING :: "A410B984-9839-4819-A0BE-2856AE6B3ADB" +IXAPO_UUID := &win.IID{0xA410B984, 0x9839, 0x4819, {0xA0, 0xBE, 0x28, 0x56, 0xAE, 0x6B, 0x3A, 0xDB}} +IXAPO :: struct #raw_union { + #subtype iunknown: IUnknown, + using ixapo_vtable: ^IXAPO_VTable, +} +IXAPO_VTable :: struct { + using iunknown_vtable: IUnknown_VTable, + + // DESCRIPTION: + // Allocates a copy of the registration properties of the XAPO. + // PARAMETERS: + // ppRegistrationProperties - [out] receives pointer to copy of registration properties, use XAPOFree to free structure, left untouched on failure + // RETURN VALUE: + // COM error code + GetRegistrationProperties: proc "system" (this: ^IXAPO, ppRegistrationProperties: ^^XAPO_REGISTRATION_PROPERTIES) -> HRESULT, + + // DESCRIPTION: + // Queries if an input/output configuration is supported. + // REMARKS: + // This method allows XAPOs to express dependency of input format with respect to output format. + // If the input/output format pair configuration is unsupported, this method also determines the nearest input format supported. + // Nearest meaning closest bit depth, framerate, and channel count, in that order of importance. + // The behaviour of this method should remain constant after the XAPO has been initialized. + // PARAMETERS: + // pOutputFormat - [in] output format known to be supported + // pRequestedInputFormat - [in] input format to examine + // ppSupportedInputFormat - [out] receives pointer to nearest input format supported if not NULL and input/output configuration unsupported, use XAPOFree to free structure, left untouched on any failure except XAPO_E_FORMAT_UNSUPPORTED + // RETURN VALUE: + // COM error code, including: + // S_OK - input/output configuration supported, ppSupportedInputFormat left untouched + // FORMAT_UNSUPPORTED - input/output configuration unsupported, ppSupportedInputFormat receives pointer to nearest input format supported if not NULL + // E_INVALIDARG - either audio format invalid, ppSupportedInputFormat left untouched + IsInputFormatSupported: proc "system" (this: ^IXAPO, pOutputFormat: ^WAVEFORMATEX, pRequestedInputFormat: ^WAVEFORMATEX, ppSupportedInputFormat: ^^WAVEFORMATEX) -> HRESULT, + + // DESCRIPTION: + // Queries if an input/output configuration is supported. + // REMARKS: + // This method allows XAPOs to express dependency of output format with respect to input format. + // If the input/output format pair configuration is unsupported, this method also determines the nearest output format supported. + // Nearest meaning closest bit depth, framerate, and channel count, in that order of importance. + // The behaviour of this method should remain constant after the XAPO has been initialized. + // PARAMETERS: + // pInputFormat - [in] input format known to be supported + // pRequestedOutputFormat - [in] output format to examine + // ppSupportedOutputFormat - [out] receives pointer to nearest output format supported if not NULL and input/output configuration unsupported, use XAPOFree to free structure, left untouched on any failure except XAPO_E_FORMAT_UNSUPPORTED + // RETURN VALUE: + // COM error code, including: + // S_OK - input/output configuration supported, ppSupportedOutputFormat left untouched + // FORMAT_UNSUPPORTED - input/output configuration unsupported, ppSupportedOutputFormat receives pointer to nearest output format supported if not NULL + // E_INVALIDARG - either audio format invalid, ppSupportedOutputFormat left untouched + IsOutputFormatSupported: proc "system" (this: ^IXAPO, pInputFormat: ^WAVEFORMATEX, pRequestedOutputFormat: ^WAVEFORMATEX, ppSupportedOutputFormat: ^^WAVEFORMATEX) -> HRESULT, + + // DESCRIPTION: + // Performs any effect-specific initialization if required. + // REMARKS: + // The contents of pData are defined by the XAPO. + // Immutable variables (constant during the lifespan of the XAPO) should be set once via this method. + // Once initialized, an XAPO cannot be initialized again. + // An XAPO should be initialized before passing it to XAudio2 as part of an effect chain. XAudio2 will not call this method; it exists for future content-driven initialization. + // PARAMETERS: + // pData - [in] effect-specific initialization parameters, may be NULL if DataByteSize == 0 + // DataByteSize - [in] size of pData in bytes, may be 0 if pData is NULL + // RETURN VALUE: + // COM error code + Initialize: proc "system" (this: ^IXAPO, pData: rawptr, DataByteSize: u32) -> HRESULT, + + // DESCRIPTION: + // Resets variables dependent on frame history. + // REMARKS: + // All other variables remain unchanged, including variables set by IXAPOParameters.SetParameters. + // For example, an effect with delay should zero out its delay line during this method, but should not reallocate anything as the + // XAPO remains locked with a constant input/output configuration. XAudio2 calls this method only if the XAPO is locked. + // This method should not block as it is called from the realtime thread. + // PARAMETERS: + // void + // RETURN VALUE: + // void + Reset: proc "system" (this: ^IXAPO), + + // DESCRIPTION: + // Locks the XAPO to a specific input/output configuration, + // allowing it to do any final initialization before Process + // is called on the realtime thread. + // REMARKS: + // Once locked, the input/output configuration and any other locked variables remain constant until UnlockForProcess is called. + // XAPOs should assert the input/output configuration is supported and that any required effect-specific initialization is complete. + // IsInputFormatSupported, IsOutputFormatSupported, and Initialize should be called as necessary before this method is called. + // All internal memory buffers required for Process should be allocated by the time this method returns successfully as Process is non-blocking and should not allocate memory. + // Once locked, an XAPO cannot be locked again until UnLockForProcess is called. + // PARAMETERS: + // InputLockedParameterCount - [in] number of input buffers, must be within [XAPO_REGISTRATION_PROPERTIES.MinInputBufferCount, XAPO_REGISTRATION_PROPERTIES.MaxInputBufferCount] + // pInputLockedParameters - [in] array of input locked buffer parameter structures, may be NULL if InputLockedParameterCount == 0, otherwise must have InputLockedParameterCount elements + // OutputLockedParameterCount - [in] number of output buffers, must be within [XAPO_REGISTRATION_PROPERTIES.MinOutputBufferCount, XAPO_REGISTRATION_PROPERTIES.MaxOutputBufferCount], must match InputLockedParameterCount when XAPO_FLAG_BUFFERCOUNT_MUST_MATCH used + // pOutputLockedParameters - [in] array of output locked buffer parameter structures, may be NULL if OutputLockedParameterCount == 0, otherwise must have OutputLockedParameterCount elements + // RETURN VALUE: + // COM error code + LockForProcess: proc "system" (this: ^IXAPO, InputLockedParameterCount: u32, pInputLockedParameters: [^]XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS, OutputLockedParameterCount: u32, pOutputLockedParameters: [^]XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS) -> HRESULT, + + // DESCRIPTION: + // Opposite of LockForProcess. Variables allocated during LockForProcess should be deallocated by this method. + // REMARKS: + // Unlocking an XAPO allows an XAPO instance to be reused with different input/output configurations. + // PARAMETERS: + // void + // RETURN VALUE: + // void + UnlockForProcess: proc "system" (this: ^IXAPO), + + // DESCRIPTION: + // Runs the XAPO's DSP code on the given input/output buffers. + // REMARKS: + // In addition to writing to the output buffers as appropriate, an XAPO must set the BufferFlags and ValidFrameCount members of all elements in pOutputProcessParameters accordingly. + // ppInputProcessParameters will not necessarily be the same as ppOutputProcessParameters for in-place processing, rather the pBuffer members of each will point to the same memory. + // Multiple input/output buffers may be used with in-place XAPOs, though the input buffer count must equal the output buffer count. + // When multiple input/output buffers are used with in-place XAPOs, the XAPO may assume input buffer [N] equals output buffer [N]. + // When IsEnabled is FALSE, the XAPO should process thru. Thru processing means an XAPO should not apply its normal processing to the given input/output buffers during Process. + // It should instead pass data from input to output with as little modification possible. Effects that perform format conversion should continue to do so. + // The effect must ensure transitions between normal and thru processing do not introduce discontinuities into the signal. + // XAudio2 calls this method only if the XAPO is locked. This method should not block as it is called from the realtime thread. + // PARAMETERS: + // InputProcessParameterCount - [in] number of input buffers, matches respective InputLockedParameterCount parameter given to LockForProcess + // pInputProcessParameters - [in] array of input process buffer parameter structures, may be NULL if InputProcessParameterCount == 0, otherwise must have InputProcessParameterCount elements + // OutputProcessParameterCount - [in] number of output buffers, matches respective OutputLockedParameterCount parameter given to LockForProcess + // pOutputProcessParameters - [in/out] array of output process buffer parameter structures, may be NULL if OutputProcessParameterCount == 0, otherwise must have OutputProcessParameterCount elements + // IsEnabled - [in] TRUE to process normally, FALSE to process thru + // RETURN VALUE: + // void + Process: proc "system" (this: ^IXAPO, InputProcessParameterCount: u32, pInputProcessParameters: [^]XAPO_PROCESS_BUFFER_PARAMETERS, OutputProcessParameterCount: u32, pOutputProcessParameters: [^]XAPO_PROCESS_BUFFER_PARAMETERS, IsEnabled: b32), + + // DESCRIPTION: + // Returns the number of input frames required to generate the requested number of output frames. + // REMARKS: + // XAudio2 may call this method to determine how many input frames an XAPO requires. + // This is constant for locked CBR XAPOs; this method need only be called once while an XAPO is locked. + // XAudio2 calls this method only if the XAPO is locked. This method should not block as it is called from the realtime thread. + // PARAMETERS: + // OutputFrameCount - [in] requested number of output frames, must be within respective [0, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount], always XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount for CBR/user-defined XAPOs + // RETURN VALUE: + // number of input frames required + CalcInputFrames: proc "system" (this: ^IXAPO, OutputFrameCount: u32) -> u32, + + // DESCRIPTION: + // Returns the number of output frames generated for the requested number of input frames. + // REMARKS: + // XAudio2 may call this method to determine how many output frames an XAPO will generate. This is constant for locked CBR XAPOs; this method need only be called once while an XAPO is locked. + // XAudio2 calls this method only if the XAPO is locked. This method should not block as it is called from the realtime thread. + // PARAMETERS: + // InputFrameCount - [in] requested number of input frames, must be within respective [0, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount], always XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount for CBR/user-defined XAPOs + // RETURN VALUE: + // number of output frames generated + CalcOutputFrames: proc "system" (this: ^IXAPO, InputFrameCount: u32) -> u32, +} + +// IXAPOParameters: +// Optional XAPO COM interface that allows an XAPO to use effect-specific parameters. +IXAPOParameters_UUID_STRING :: "26D95C66-80F2-499A-AD54-5AE7F01C6D98" +IXAPOParameters_UUID := &win.IID{0x26D95C66, 0x80F2, 0x499A, {0xAD, 0x54, 0x5A, 0xE7, 0xF0, 0x1C, 0x6D, 0x98}} +IXAPOParameters :: struct #raw_union { + #subtype iunknown: IUnknown, + using ixapoparameters_vtable: ^IXAPOParameters_VTable, +} +IXAPOParameters_VTable :: struct { + using iunknown_vtable: IUnknown_VTable, + + // DESCRIPTION: + // Sets effect-specific parameters. + // REMARKS: + // This method may only be called on the realtime thread; no synchronization between it and IXAPO.Process is necessary. + // This method should not block as it is called from the realtime thread. + // PARAMETERS: + // pParameters - [in] effect-specific parameter block, must be != NULL + // ParameterByteSize - [in] size of pParameters in bytes, must be > 0 + // RETURN VALUE: + // void + SetParameters: proc "system" (this: ^IXAPOParameters, pParameters: rawptr, ParameterByteSize: u32), + + // DESCRIPTION: + // Gets effect-specific parameters. + // REMARKS: + // Unlike SetParameters, XAudio2 does not call this method on the realtime thread. Thus, the XAPO must protect variables shared with SetParameters/Process using appropriate synchronization. + // PARAMETERS: + // pParameters - [out] receives effect-specific parameter block, must be != NULL + // ParameterByteSize - [in] size of pParameters in bytes, must be > 0 + // RETURN VALUE: + // void + GetParameters: proc "system" (this: ^IXAPOParameters, pParameters: rawptr, ParameterByteSize: u32), +} diff --git a/vendor/windows/XAudio2/xapofx.odin b/vendor/windows/XAudio2/xapofx.odin new file mode 100644 index 000000000..5c4c8c7ec --- /dev/null +++ b/vendor/windows/XAudio2/xapofx.odin @@ -0,0 +1,138 @@ +#+build windows + +package windows_xaudio2 + +import win "core:sys/windows" + +foreign import xa2 "system:xaudio2.lib" + +//---------------------------------------------------// + +FXEQ_UUID_STRING :: "F5E01117-D6C4-485A-A3F5-695196F3DBFA" +FXEQ_UUID := &win.CLSID{0xF5E01117, 0xD6C4, 0x485A, {0xA3, 0xF5, 0x69, 0x51, 0x96, 0xF3, 0xDB, 0xFA}} + +FXMasteringLimiter_UUID_STRING :: "C4137916-2BE1-46FD-8599-441536F49856" +FXMasteringLimiter_UUID := &win.CLSID{0xC4137916, 0x2BE1, 0x46FD, {0x85, 0x99, 0x44, 0x15, 0x36, 0xF4, 0x98, 0x56}} + +FXReverb_UUID_STRING :: "7D9ACA56-CB68-4807-B632-B137352E8596" +FXReverb_UUID := &win.CLSID{0x7D9ACA56, 0xCB68, 0x4807, {0xB6, 0x32, 0xB1, 0x37, 0x35, 0x2E, 0x85, 0x96}} + +FXEcho_UUID_STRING :: "5039D740-F736-449A-84D3-A56202557B87" +FXEcho_UUID := &win.CLSID{0x5039D740, 0xF736, 0x449A, {0x84, 0xD3, 0xA5, 0x62, 0x02, 0x55, 0x7B, 0x87}} + +// EQ parameter bounds (inclusive), used with FXEQ: +FXEQ_MIN_FRAMERATE :: 22000 +FXEQ_MAX_FRAMERATE :: 48000 + +FXEQ_MIN_FREQUENCY_CENTER :: 20.0 +FXEQ_MAX_FREQUENCY_CENTER :: 20000.0 +FXEQ_DEFAULT_FREQUENCY_CENTER_0 :: 100.0 // band 0 +FXEQ_DEFAULT_FREQUENCY_CENTER_1 :: 800.0 // band 1 +FXEQ_DEFAULT_FREQUENCY_CENTER_2 :: 2000.0 // band 2 +FXEQ_DEFAULT_FREQUENCY_CENTER_3 :: 10000.0 // band 3 + +FXEQ_MIN_GAIN :: 0.126 // -18dB +FXEQ_MAX_GAIN :: 7.94 // +18dB +FXEQ_DEFAULT_GAIN :: 1.0 // 0dB change, all bands + +FXEQ_MIN_BANDWIDTH :: 0.1 +FXEQ_MAX_BANDWIDTH :: 2.0 +FXEQ_DEFAULT_BANDWIDTH :: 1.0 // all bands + + +// Mastering limiter parameter bounds (inclusive), used with FXMasteringLimiter: +FXMASTERINGLIMITER_MIN_RELEASE :: 1 +FXMASTERINGLIMITER_MAX_RELEASE :: 20 +FXMASTERINGLIMITER_DEFAULT_RELEASE :: 6 + +FXMASTERINGLIMITER_MIN_LOUDNESS :: 1 +FXMASTERINGLIMITER_MAX_LOUDNESS :: 1800 +FXMASTERINGLIMITER_DEFAULT_LOUDNESS :: 1000 + + +// Reverb parameter bounds (inclusive), used with FXReverb: +FXREVERB_MIN_DIFFUSION :: 0.0 +FXREVERB_MAX_DIFFUSION :: 1.0 +FXREVERB_DEFAULT_DIFFUSION :: 0.9 + +FXREVERB_MIN_ROOMSIZE :: 0.0001 +FXREVERB_MAX_ROOMSIZE :: 1.0 +FXREVERB_DEFAULT_ROOMSIZE :: 0.6 + +// Loudness defaults used with FXLoudness: +FXLOUDNESS_DEFAULT_MOMENTARY_MS :: 400 +FXLOUDNESS_DEFAULT_SHORTTERM_MS :: 3000 + +// Echo initialization data/parameter bounds (inclusive), used with FXEcho: +FXECHO_MIN_WETDRYMIX :: 0.0 +FXECHO_MAX_WETDRYMIX :: 1.0 +FXECHO_DEFAULT_WETDRYMIX :: 0.5 + +FXECHO_MIN_FEEDBACK :: 0.0 +FXECHO_MAX_FEEDBACK :: 1.0 +FXECHO_DEFAULT_FEEDBACK :: 0.5 + +FXECHO_MIN_DELAY :: 1.0 +FXECHO_MAX_DELAY :: 2000.0 +FXECHO_DEFAULT_DELAY :: 500.0 + +//-----------------------------------------------------// + +// EQ parameters (4 bands), used with IXAPOParameters.SetParameters: +// The EQ supports only f32 audio foramts. +// The framerate must be within [22000, 48000] Hz. +FXEQ_PARAMETERS :: struct #packed { + FrequencyCenter0: f32, // center frequency in Hz, band 0 + Gain0: f32, // boost/cut + Bandwidth0: f32, // bandwidth, region of EQ is center frequency +/- bandwidth/2 + FrequencyCenter1: f32, // band 1 + Gain1: f32, + Bandwidth1: f32, + FrequencyCenter2: f32, // band 2 + Gain2: f32, + Bandwidth2: f32, + FrequencyCenter3: f32, // band 3 + Gain3: f32, + Bandwidth3: f32, +} + +// Mastering limiter parameters, used with IXAPOParameters.SetParameters: +// The mastering limiter supports only f32 audio formats. +FXMASTERINGLIMITER_PARAMETERS :: struct #packed { + Release: u32, // release time (tuning factor with no specific units) + Loudness: u32, // loudness target (threshold) +} + +// Reverb parameters, used with IXAPOParameters.SetParameters: +// The reverb supports only f32 audio formats with the following channel configurations: +// Input: Mono Output: Mono +// Input: Stereo Output: Stereo +FXREVERB_PARAMETERS :: struct #packed { + Diffusion: f32, // diffusion + RoomSize: f32, // room size +} + + +// Echo initialization data, used with CreateFX: +// Use of this structure is optional, the default MaxDelay is FXECHO_DEFAULT_DELAY. +FXECHO_INITDATA :: struct #packed { + MaxDelay: f32, // maximum delay (all channels) in milliseconds, must be within [FXECHO_MIN_DELAY, FXECHO_MAX_DELAY] +} + +// Echo parameters, used with IXAPOParameters.SetParameters: +// The echo supports only f32 audio formats. +FXECHO_PARAMETERS :: struct #packed { + WetDryMix: f32, // ratio of wet (processed) signal to dry (original) signal + Feedback: f32, // amount of output fed back into input + Delay: f32, // delay (all channels) in milliseconds, must be within [FXECHO_MIN_DELAY, FXECHO_PARAMETERS.MaxDelay] +} + +//-------------------------------------------------------// + +@(default_calling_convention="cdecl") +foreign xa2 { + // creates instance of requested XAPO, use Release to free instance + // pInitData - [in] effect-specific initialization parameters, may be NULL if InitDataByteSize == 0 + // InitDataByteSize - [in] size of pInitData in bytes, may be 0 if pInitData is NULL + CreateFX :: proc(clsid: win.REFCLSID, pEffect: ^^IUnknown, pInitDat: rawptr = nil, InitDataByteSize: u32 = 0) -> HRESULT --- +} diff --git a/vendor/windows/XAudio2/xaudio2.odin b/vendor/windows/XAudio2/xaudio2.odin new file mode 100644 index 000000000..078ec4095 --- /dev/null +++ b/vendor/windows/XAudio2/xaudio2.odin @@ -0,0 +1,839 @@ +#+build windows +/* + Bindings for Windows XAudio2: + https://learn.microsoft.com/en-us/windows/win32/xaudio2/xaudio2-introduction + + Compiling for Windows 10 RS5 (1809) and later +*/ + +package windows_xaudio2 + +import win "core:sys/windows" +import "core:math" + +HRESULT :: win.HRESULT +IUnknown :: win.IUnknown +IUnknown_VTable :: win.IUnknown_VTable +WAVEFORMATEX :: win.WAVEFORMATEX + +/************************************************************************** + * + * XAudio2 constants, flags and error codes. + * + **************************************************************************/ + +// Numeric boundary values +MAX_BUFFER_BYTES :: 0x80000000 // Maximum bytes allowed in a source buffer +MAX_QUEUED_BUFFERS :: 64 // Maximum buffers allowed in a voice queue +MAX_BUFFERS_SYSTEM :: 2 // Maximum buffers allowed for system threads (Xbox 360 only) +MAX_AUDIO_CHANNELS :: 64 // Maximum channels in an audio stream +MIN_SAMPLE_RATE :: 1000 // Minimum audio sample rate supported +MAX_SAMPLE_RATE :: 200000 // Maximum audio sample rate supported +MAX_VOLUME_LEVEL :: 16777216.0 // Maximum acceptable volume level (2^24) +MIN_FREQ_RATIO :: (1.0 / 1024.0) // Minimum SetFrequencyRatio argument +MAX_FREQ_RATIO :: 1024.0 // Maximum MaxFrequencyRatio argument +DEFAULT_FREQ_RATIO :: 2.0 // Default MaxFrequencyRatio argument +MAX_FILTER_ONEOVERQ :: 1.5 // Maximum FILTER_PARAMETERS.OneOverQ +MAX_FILTER_FREQUENCY :: 1.0 // Maximum FILTER_PARAMETERS.Frequency +MAX_LOOP_COUNT :: 254 // Maximum non-infinite BUFFER.LoopCount +MAX_INSTANCES :: 8 // Maximum simultaneous XAudio2 objects on Xbox 360 + +// For XMA voices on Xbox 360 there is an additional restriction on the MaxFrequencyRatio argument and the voice's sample rate: the product of these numbers cannot exceed 600000 for one-channel voices or 300000 for voices with more than one channel. +MAX_RATIO_TIMES_RATE_XMA_MONO :: 600000 +MAX_RATIO_TIMES_RATE_XMA_MULTICHANNEL :: 300000 + +// Numeric values with special meanings +COMMIT_NOW :: 0 // Used as an OperationSet argument +COMMIT_ALL :: 0 // Used in IXAudio2.CommitChanges +INVALID_OPSET :: 0xffffffff // Not allowed for OperationSet arguments +NO_LOOP_REGION :: 0 // Used in BUFFER.LoopCount +LOOP_INFINITE :: 255 // Used in BUFFER.LoopCount +DEFAULT_CHANNELS :: 0 // Used in CreateMasteringVoice +DEFAULT_SAMPLERATE :: 0 // Used in CreateMasteringVoice + +// Flags +FLAGS :: distinct bit_set[FLAG; u32] +FLAG :: enum u32 { + DEBUG_ENGINE = 0, // Used in Create + VOICE_NOPITCH = 1, // Used in IXAudio2.CreateSourceVoice + VOICE_NOSRC = 2, // Used in IXAudio2.CreateSourceVoice + VOICE_USEFILTER = 3, // Used in IXAudio2.CreateSource/SubmixVoice + PLAY_TAILS = 5, // Used in IXAudio2SourceVoice.Stop + END_OF_STREAM = 6, // Used in BUFFER.Flags + SEND_USEFILTER = 7, // Used in SEND_DESCRIPTOR.Flags + VOICE_NOSAMPLESPLAYED = 8, // Used in IXAudio2SourceVoice.GetState + STOP_ENGINE_WHEN_IDLE = 13, // Used in Create to force the engine to Stop when no source voices are Started, and Start when a voice is Started + QUANTUM_1024 = 15, // Used in Create to specify nondefault processing quantum of 21.33 ms (1024 samples at 48KHz) + NO_VIRTUAL_AUDIO_CLIENT = 16, // Used in CreateMasteringVoice to create a virtual audio client +} + +// Default parameters for the built-in filter +DEFAULT_FILTER_TYPE :: FILTER_TYPE.LowPassFilter +DEFAULT_FILTER_FREQUENCY :: MAX_FILTER_FREQUENCY +DEFAULT_FILTER_ONEOVERQ :: 1.0 + +// Internal XAudio2 constants +// The audio frame quantum can be calculated by reducing the fraction: +// SamplesPerAudioFrame / SamplesPerSecond +QUANTUM_NUMERATOR :: 1 // On Windows, XAudio2 processes audio +QUANTUM_DENOMINATOR :: 100 // in 10ms chunks (= 1/100 seconds) +QUANTUM_MS :: (1000.0 * QUANTUM_NUMERATOR / QUANTUM_DENOMINATOR) + +// XAudio2 error codes +INVALID_CALL :: HRESULT(-2003435519) // 0x88960001 An API call or one of its arguments was illegal +XMA_DECODER_ERROR :: HRESULT(-2003435518) // 0x88960002 The XMA hardware suffered an unrecoverable error +XAPO_CREATION_FAILED :: HRESULT(-2003435517) // 0x88960003 XAudio2 failed to initialize an XAPO effect +DEVICE_INVALIDATED :: HRESULT(-2003435516) // 0x88960004 An audio device became unusable (unplugged, etc) + + +/************************************************************************** + * + * XAudio2 structures and enumerations. + * + **************************************************************************/ + +// Used in Create, specifies which CPU(s) to use. +PROCESSOR_FLAGS :: distinct bit_set[PROCESOR_FLAG; u32] +PROCESOR_FLAG :: enum u32 { + Processor1 = 0, + Processor2 = 1, + Processor3 = 2, + Processor4 = 3, + Processor5 = 4, + Processor6 = 5, + Processor7 = 6, + Processor8 = 7, + Processor9 = 8, + Processor10 = 9, + Processor11 = 10, + Processor12 = 11, + Processor13 = 12, + Processor14 = 13, + Processor15 = 14, + Processor16 = 15, + Processor17 = 16, + Processor18 = 17, + Processor19 = 18, + Processor20 = 19, + Processor21 = 20, + Processor22 = 21, + Processor23 = 22, + Processor24 = 23, + Processor25 = 24, + Processor26 = 25, + Processor27 = 26, + Processor28 = 27, + Processor29 = 28, + Processor30 = 29, + Processor31 = 30, + Processor32 = 31, +} + +USE_DEFAULT_PROCESSOR :: PROCESSOR_FLAGS{} + +// Returned by IXAudio2Voice.GetVoiceDetails +VOICE_DETAILS :: struct #packed { + CreatingFlags: FLAGS, + ActiveFlags: FLAGS, + InputChannels: u32, + InputSampleRate: u32, +} + +// Used in VOICE_SENDS below +SEND_DESCRIPTOR :: struct #packed { + Flags: FLAGS, // Either 0 or SEND_USEFILTER. + pOutputVoice: ^IXAudio2Voice, // This send's destination voice. +} + +// Used in the voice creation functions and in IXAudio2Voice.SetOutputVoices +VOICE_SENDS :: struct #packed { + SendCount: u32, // Number of sends from this voice. + pSends: [^]SEND_DESCRIPTOR, // Array of SendCount send descriptors. +} + +// Used in EFFECT_CHAIN below +EFFECT_DESCRIPTOR :: struct #packed { + pEffect: ^IUnknown, // Pointer to the effect object's IUnknown interface. + InitialState: b32, // TRUE if the effect should begin in the enabled state. + OutputChannels: u32, // How many output channels the effect should produce. +} + +// Used in the voice creation functions and in IXAudio2Voice.SetEffectChain +EFFECT_CHAIN :: struct #packed { + EffectCount: u32, // Number of effects in this voice's effect chain. + pEffectDescriptors: [^]EFFECT_DESCRIPTOR, // Array of effect descriptors. +} + +// Used in FILTER_PARAMETERS below +FILTER_TYPE :: enum i32 { + LowPassFilter, // Attenuates frequencies above the cutoff frequency (state-variable filter). + BandPassFilter, // Attenuates frequencies outside a given range (state-variable filter). + HighPassFilter, // Attenuates frequencies below the cutoff frequency (state-variable filter). + NotchFilter, // Attenuates frequencies inside a given range (state-variable filter). + LowPassOnePoleFilter, // Attenuates frequencies above the cutoff frequency (one-pole filter, FILTER_PARAMETERS.OneOverQ has no effect) + HighPassOnePoleFilter, // Attenuates frequencies below the cutoff frequency (one-pole filter, FILTER_PARAMETERS.OneOverQ has no effect) +} + +// Used in IXAudio2Voice.Set/GetFilterParameters and Set/GetOutputFilterParameters +FILTER_PARAMETERS :: struct #packed { + Type: FILTER_TYPE, // Filter type. + Frequency: f32, // Filter coefficient. Must be >= 0 and <= MAX_FILTER_FREQUENCY. See CutoffFrequencyToRadians() for state-variable filter types and CutoffFrequencyToOnePoleCoefficient() for one-pole filter types. + OneOverQ: f32, // Reciprocal of the filter's quality factor Q; must be > 0 and <= MAX_FILTER_ONEOVERQ. Has no effect for one-pole filters. +} + +// Used in IXAudio2SourceVoice.SubmitSourceBuffer +BUFFER :: struct #packed { + Flags: FLAGS, // Either 0 or END_OF_STREAM. + AudioBytes: u32, // Size of the audio data buffer in bytes. + pAudioData: [^]byte, // Pointer to the audio data buffer. + PlayBegin: u32, // First sample in this buffer to be played. + PlayLength: u32, // Length of the region to be played in samples, or 0 to play the whole buffer. + LoopBegin: u32, // First sample of the region to be looped. + LoopLength: u32, // Length of the desired loop region in samples, or 0 to loop the entire buffer. + LoopCount: u32, // Number of times to repeat the loop region, or LOOP_INFINITE to loop forever. + pContext: rawptr, // Context value to be passed back in callbacks. +} + +// Used in IXAudio2SourceVoice.SubmitSourceBuffer when submitting XWMA data. +// NOTE: If an XWMA sound is submitted in more than one buffer, each buffer's pDecodedPacketCumulativeBytes[PacketCount-1] value must be subtracted from all the entries in the next buffer's pDecodedPacketCumulativeBytes array. +// And whether a sound is submitted in more than one buffer or not, the final buffer of the sound should use the END_OF_STREAM flag, or else the client must call IXAudio2SourceVoice.Discontinuity after submitting it. +BUFFER_WMA :: struct #packed { + pDecodedPacketCumulativeBytes: [^]u32, // Decoded packet's cumulative size array. Each element is the number of bytes accumulated when the corresponding XWMA packet is decoded in order. The array must have PacketCount elements. + PacketCount: u32, // Number of XWMA packets submitted. Must be >= 1 and divide evenly into BUFFER.AudioBytes. +} + +// Returned by IXAudio2SourceVoice.GetState +VOICE_STATE :: struct #packed { + pCurrentBufferContext: rawptr, // The pContext value provided in the BUFFER that is currently being processed, or NULL if there are no buffers in the queue. + BuffersQueued: u32, // Number of buffers currently queued on the voice (including the one that is being processed). + SamplesPlayed: u64, // Total number of samples produced by the voice since it began processing the current audio stream. If VOICE_NOSAMPLESPLAYED is specified in the call to IXAudio2SourceVoice.GetState, this member will not be calculated, saving CPU. +} + +// Returned by IXAudio2.GetPerformanceData +PERFORMANCE_DATA :: struct #packed { + // CPU usage information + AudioCyclesSinceLastQuery: u64, // CPU cycles spent on audio processing since the last call to StartEngine or GetPerformanceData. + TotalCyclesSinceLastQuery: u64, // Total CPU cycles elapsed since the last call (only counts the CPU XAudio2 is running on). + MinimumCyclesPerQuantum: u32, // Fewest CPU cycles spent processing any one audio quantum since the last call. + MaximumCyclesPerQuantum: u32, // Most CPU cycles spent processing any one audio quantum since the last call. + + // Memory usage information + MemoryUsageInBytes: u32, // Total heap space currently in use. + + // Audio latency and glitching information + CurrentLatencyInSamples: u32, // Minimum delay from when a sample is read from a source buffer to when it reaches the speakers. + GlitchesSinceEngineStarted: u32, // Audio dropouts since the engine was started. + + // Data about XAudio2's current workload + ActiveSourceVoiceCount: u32, // Source voices currently playing. + TotalSourceVoiceCount: u32, // Source voices currently existing. + ActiveSubmixVoiceCount: u32, // Submix voices currently playing/existing. + + ActiveResamplerCount: u32, // Resample xAPOs currently active. + ActiveMatrixMixCount: u32, // MatrixMix xAPOs currently active. + + // Usage of the hardware XMA decoder (Xbox 360 only) + ActiveXmaSourceVoices: u32, // Number of source voices decoding XMA data. + ActiveXmaStreams: u32, // A voice can use more than one XMA stream. +} + +// Used in IXAudio2.SetDebugConfiguration +DEBUG_CONFIGURATION :: struct #packed { + TraceMask: DEBUG_CONFIG_FLAGS, // Bitmap of enabled debug message types. + BreakMask: DEBUG_CONFIG_FLAGS, // Message types that will break into the debugger. + LogThreadID: b32, // Whether to log the thread ID with each message. + LogFileline: b32, // Whether to log the source file and line number. + LogFunctionName: b32, // Whether to log the function name. + LogTiming: b32, // Whether to log message timestamps. +} + +// Values for the TraceMask and BreakMask bitmaps. Only ERRORS and WARNINGS are valid in BreakMask. +// WARNINGS implies ERRORS, DETAIL implies INFO, and FUNC_CALLS implies API_CALLS. +// By default, TraceMask is ERRORS and WARNINGS and all the other settings are zero. +DEBUG_CONFIG_FLAGS :: distinct bit_set[DEBUG_CONFIG_FLAG; u32] +DEBUG_CONFIG_FLAG :: enum u32 { + ERRORS = 0, // For handled errors with serious effects. + WARNINGS = 1, // For handled errors that may be recoverable. + INFO = 2, // Informational chit-chat (e.g. state changes). + DETAIL = 3, // More detailed chit-chat. + API_CALLS = 4, // Public API function entries and exits. + FUNC_CALLS = 5, // Internal function entries and exits. + TIMING = 6, // Delays detected and other timing data. + LOCKS = 7, // Usage of critical sections and mutexes. + MEMORY = 8, // Memory heap usage information. + STREAMING = 12, // Audio streaming information. +} + +/************************************************************************** + * + * IXAudio2: Top-level XAudio2 COM interface. + * + **************************************************************************/ + +IXAudio2_UUID_STRING :: "2B02E3CF-2E0B-4ec3-BE45-1B2A3FE7210D" +IXAudio2_UUID := &win.IID{0x2B02E3CF, 0x2E0B, 0x4ec3, {0xBE, 0x45, 0x1B, 0x2A, 0x3F, 0xE7, 0x21, 0x0D}} +IXAudio2 :: struct #raw_union { + #subtype iunknown: IUnknown, + using ixaudio2_vtable: ^IXAudio2_VTable, +} +IXAudio2_VTable :: struct { + using iunknown_vtable: IUnknown_VTable, + + // NAME: IXAudio2.RegisterForCallbacks + // DESCRIPTION: Adds a new client to receive XAudio2's engine callbacks. + // ARGUMENTS: + // pCallback - Callback interface to be called during each processing pass. + RegisterForCallbacks: proc "system" (this: ^IXAudio2, pCallback: ^IXAudio2EngineCallback) -> HRESULT, + + // NAME: IXAudio2.UnregisterForCallbacks + // DESCRIPTION: Removes an existing receiver of XAudio2 engine callbacks. + // ARGUMENTS: + // pCallback - Previously registered callback interface to be removed. + UnregisterForCallbacks: proc "system" (this: ^IXAudio2, pCallback: ^IXAudio2EngineCallback), + + // NAME: IXAudio2.CreateSourceVoice + // DESCRIPTION: Creates and configures a source voice. + // ARGUMENTS: + // ppSourceVoice - Returns the new object's IXAudio2SourceVoice interface. + // pSourceFormat - Format of the audio that will be fed to the voice. + // Flags - VOICE flags specifying the source voice's behavior. + // MaxFrequencyRatio - Maximum SetFrequencyRatio argument to be allowed. + // pCallback - Optional pointer to a client-provided callback interface. + // pSendList - Optional list of voices this voice should send audio to. + // pEffectChain - Optional list of effects to apply to the audio data. + CreateSourceVoice: proc "system" (this: ^IXAudio2, ppSourceVoice: ^^IXAudio2SourceVoice, pSourceFormat: ^WAVEFORMATEX, Flags: FLAGS = {}, MaxFrequencyRatio: f32 = DEFAULT_FREQ_RATIO, pCallback: ^IXAudio2VoiceCallback = nil, pSendList: [^]VOICE_SENDS = nil, pEffectChain: [^]EFFECT_CHAIN = nil) -> HRESULT, + + // NAME: IXAudio2.CreateSubmixVoice + // DESCRIPTION: Creates and configures a submix voice. + // ARGUMENTS: + // ppSubmixVoice - Returns the new object's IXAudio2SubmixVoice interface. + // InputChannels - Number of channels in this voice's input audio data. + // InputSampleRate - Sample rate of this voice's input audio data. + // Flags - VOICE flags specifying the submix voice's behavior. + // ProcessingStage - Arbitrary number that determines the processing order. + // pSendList - Optional list of voices this voice should send audio to. + // pEffectChain - Optional list of effects to apply to the audio data. + CreateSubmixVoice: proc "system" (this: ^IXAudio2, ppSubmixVoice: ^^IXAudio2SubmixVoice, InputChannels: u32, InputSampleRate: u32, Flags: FLAGS = {}, ProcessingStage: u32 = 0, pSendList: [^]VOICE_SENDS = nil, pEffectChain: [^]EFFECT_CHAIN = nil) -> HRESULT, + + // NAME: IXAudio2.CreateMasteringVoice + // DESCRIPTION: Creates and configures a mastering voice. + // ARGUMENTS: + // ppMasteringVoice - Returns the new object's IXAudio2MasteringVoice interface. + // InputChannels - Number of channels in this voice's input audio data. + // InputSampleRate - Sample rate of this voice's input audio data. + // Flags - VOICE flags specifying the mastering voice's behavior. + // szDeviceId - Identifier of the device to receive the output audio. + // pEffectChain - Optional list of effects to apply to the audio data. + // StreamCategory - The audio stream category to use for this mastering voice + CreateMasteringVoice: proc "system" (this: ^IXAudio2, ppMasteringVoice: ^^IXAudio2MasteringVoice, InputChannels: u32 = DEFAULT_CHANNELS, InputSampleRate: u32 = DEFAULT_SAMPLERATE, Flags: FLAGS = {}, szDeviceId: win.LPCWSTR = nil, pEffectChain: [^]EFFECT_CHAIN = nil, StreamCategory: AUDIO_STREAM_CATEGORY = .GameEffects) -> HRESULT, + + // NAME: IXAudio2.:StartEngine + // DESCRIPTION: Creates and starts the audio processing thread. + StartEngine: proc "system" (this: ^IXAudio2) -> HRESULT, + + // NAME: IXAudio2.StopEngine + // DESCRIPTION: Stops and destroys the audio processing thread. + StopEngine: proc "system" (this: ^IXAudio2), + + // NAME: IXAudio2.CommitChanges + // DESCRIPTION: Atomically applies a set of operations previously tagged + // with a given identifier. + // ARGUMENTS: + // OperationSet - Identifier of the set of operations to be applied. + CommitChanges: proc "system" (this: ^IXAudio2, OperationSet: u32) -> HRESULT, + + // NAME: IXAudio2.GetPerformanceData + // DESCRIPTION: Returns current resource usage details: memory, CPU, etc. + // ARGUMENTS: + // pPerfData - Returns the performance data structure. + GetPerformanceData: proc "system" (this: ^IXAudio2, pPerfData: ^PERFORMANCE_DATA), + + // NAME: IXAudio2.SetDebugConfiguration + // DESCRIPTION: Configures XAudio2's debug output (in debug builds only). + // ARGUMENTS: + // pDebugConfiguration - Structure describing the debug output behavior. + // pReserved - Optional parameter; must be NULL. + SetDebugConfiguration: proc "system" (this: ^IXAudio2, pDebugConfiguration: ^DEBUG_CONFIGURATION, pReserved: rawptr = nil), +} + +// This interface extends IXAudio2 with additional functionality. +// Use IXAudio2.QueryInterface to obtain a pointer to this interface. +IXAudio2Extension_UUID_STRING :: "84ac29bb-d619-44d2-b197-e4acf7df3ed6" +IXAudio2Extension_UUID := &win.IID{0x84ac29bb, 0xd619, 0x44d2, {0xb1, 0x97, 0xe4, 0xac, 0xf7, 0xdf, 0x3e, 0xd6}} +IXAudio2Extension :: struct #raw_union { + #subtype iunknown: IUnknown, + using ixaudio2extension_vtable: ^IXAudio2Extension_VTable, +} +IXAudio2Extension_VTable :: struct { + using iunknown_vtable: IUnknown_VTable, + + // NAME: IXAudio2Extension.GetProcessingQuantum + // DESCRIPTION: Returns the processing quantum + // quantumMilliseconds = (1000.0f * quantumNumerator / quantumDenominator) + // ARGUMENTS: + // quantumNumerator - Quantum numerator + // quantumDenominator - Quantum denominator + GetProcessingQuantum: proc "system" (this: ^IXAudio2Extension, quantumNumerator: ^u32, quantumDenominator: ^u32), + + // NAME: IXAudio2Extension.GetProcessor + // DESCRIPTION: Returns the number of the processor used by XAudio2 + // ARGUMENTS: + // processor - Non-zero Processor number + GetProcessor: proc "system" (this: ^IXAudio2Extension, processor: ^PROCESSOR_FLAGS), +} + +/************************************************************************** + * + * IXAudio2Voice: Base voice management interface. + * + **************************************************************************/ + +IXAudio2Voice :: struct { + using ixaudio2voice_vtable: ^IXAudio2Voice_VTable, +} +IXAudio2Voice_VTable :: struct { + // NAME: IXAudio2Voice.GetVoiceDetails + // DESCRIPTION: Returns the basic characteristics of this voice. + // ARGUMENTS: + // pVoiceDetails - Returns the voice's details. + GetVoiceDetails: proc "system" (this: ^IXAudio2Voice, pVoiceDetails: ^VOICE_DETAILS), + + // NAME: IXAudio2Voice.SetOutputVoices + // DESCRIPTION: Replaces the set of submix/mastering voices that receive + // this voice's output. + // ARGUMENTS: + // pSendList - Optional list of voices this voice should send audio to. + SetOutputVoices: proc "system" (this: ^IXAudio2Voice, pSendList: [^]VOICE_SENDS) -> HRESULT, + + // NAME: IXAudio2Voice.SetEffectChain + // DESCRIPTION: Replaces this voice's current effect chain with a new one. + // ARGUMENTS: + // pEffectChain - Structure describing the new effect chain to be used. + SetEffectChain: proc "system" (this: ^IXAudio2Voice, pEffectChain: ^EFFECT_CHAIN) -> HRESULT, + + // NAME: IXAudio2Voice.EnableEffect + // DESCRIPTION: Enables an effect in this voice's effect chain. + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // OperationSet - Used to identify this call as part of a deferred batch. + EnableEffect: proc "system" (this: ^IXAudio2Voice, EffectIndex: u32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.DisableEffect + // DESCRIPTION: Disables an effect in this voice's effect chain. + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // OperationSet - Used to identify this call as part of a deferred batch. + DisableEffect: proc "system" (this: ^IXAudio2Voice, EffectIndex: u32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetEffectState + // DESCRIPTION: Returns the running state of an effect. + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // pEnabled - Returns the enabled/disabled state of the given effect. + GetEffectState: proc "system" (this: ^IXAudio2Voice, EffectIndex: u32, pEnabled: ^b32), + + // NAME: IXAudio2Voice.SetEffectParameters + // DESCRIPTION: Sets effect-specific parameters. + // REMARKS: Unlike IXAPOParameters.SetParameters, this method may be called from any thread. XAudio2 implements appropriate synchronization to copy the parameters to the realtime audio processing thread. + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // pParameters - Pointer to an effect-specific parameters block. + // ParametersByteSize - Size of the pParameters array in bytes. + // OperationSet - Used to identify this call as part of a deferred batch. + SetEffectParameters: proc "system" (this: ^IXAudio2Voice, EffectIndex: u32, pParameters: rawptr, ParametersByteSize: u32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetEffectParameters + // DESCRIPTION: Obtains the current effect-specific parameters. + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // pParameters - Returns the current values of the effect-specific parameters. + // ParametersByteSize - Size of the pParameters array in bytes. + GetEffectParameters: proc "system" (this: ^IXAudio2Voice, EffectIndex: u32, pParameters: rawptr, ParametersByteSize: u32) -> HRESULT, + + // NAME: IXAudio2Voice.SetFilterParameters + // DESCRIPTION: Sets this voice's filter parameters. + // ARGUMENTS: + // pParameters - Pointer to the filter's parameter structure. + // OperationSet - Used to identify this call as part of a deferred batch. + SetFilterParameters: proc "system" (this: ^IXAudio2Voice, pParameters: ^FILTER_PARAMETERS, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetFilterParameters + // DESCRIPTION: Returns this voice's current filter parameters. + // ARGUMENTS: + // pParameters - Returns the filter parameters. + GetFilterParameters: proc "system" (this: ^IXAudio2Voice, pParameters: ^FILTER_PARAMETERS), + + // NAME: IXAudio2Voice.SetOutputFilterParameters + // DESCRIPTION: Sets the filter parameters on one of this voice's sends. + // ARGUMENTS: + // pDestinationVoice - Destination voice of the send whose filter parameters will be set. + // pParameters - Pointer to the filter's parameter structure. + // OperationSet - Used to identify this call as part of a deferred batch. + SetOutputFilterParameters: proc "system" (this: ^IXAudio2Voice, pDestinationVoice: ^IXAudio2Voice, pParameters: ^FILTER_PARAMETERS, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetOutputFilterParameters + // DESCRIPTION: Returns the filter parameters from one of this voice's sends. + // ARGUMENTS: + // pDestinationVoice - Destination voice of the send whose filter parameters will be read. + // pParameters - Returns the filter parameters. + GetOutputFilterParameters: proc "system" (this: ^IXAudio2Voice, pDestinationVoice: ^IXAudio2Voice, pParameters: ^FILTER_PARAMETERS), + + // NAME: IXAudio2Voice.SetVolume + // DESCRIPTION: Sets this voice's overall volume level. + // ARGUMENTS: + // Volume - New overall volume level to be used, as an amplitude factor. + // OperationSet - Used to identify this call as part of a deferred batch. + SetVolume: proc "system" (this: ^IXAudio2Voice, Volume: f32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetVolume + // DESCRIPTION: Obtains this voice's current overall volume level. + // ARGUMENTS: + // pVolume: Returns the voice's current overall volume level. + GetVolume: proc "system" (this: ^IXAudio2Voice, pVolume: ^f32), + + // NAME: IXAudio2Voice.SetChannelVolumes + // DESCRIPTION: Sets this voice's per-channel volume levels. + // ARGUMENTS: + // Channels - Used to confirm the voice's channel count. + // pVolumes - Array of per-channel volume levels to be used. + // OperationSet - Used to identify this call as part of a deferred batch. + SetChannelVolumes: proc "system" (this: ^IXAudio2Voice, Channels: u32, pVolumes: [^]f32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetChannelVolumes + // DESCRIPTION: Returns this voice's current per-channel volume levels. + // ARGUMENTS: + // Channels - Used to confirm the voice's channel count. + // pVolumes - Returns an array of the current per-channel volume levels. + GetChannelVolumes: proc "system" (this: ^IXAudio2Voice, Channels: u32, pVolumes: [^]f32), + + // NAME: IXAudio2Voice.SetOutputMatrix + // DESCRIPTION: Sets the volume levels used to mix from each channel of this voice's output audio to each channel of a given destination voice's input audio. + // ARGUMENTS: + // pDestinationVoice - The destination voice whose mix matrix to change. + // SourceChannels - Used to confirm this voice's output channel count (the number of channels produced by the last effect in the chain). + // DestinationChannels - Confirms the destination voice's input channels. + // pLevelMatrix - Array of [SourceChannels * DestinationChannels] send levels. The level used to send from source channel S to destination channel D should be in pLevelMatrix[S + SourceChannels * D]. + // OperationSet - Used to identify this call as part of a deferred batch. + SetOutputMatrix: proc "system" (this: ^IXAudio2Voice, pDestinationVoice: ^IXAudio2Voice, SourceChannels: u32, DestinationChannels: u32, pLevelMatrix: [^]f32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2Voice.GetOutputMatrix + // DESCRIPTION: Obtains the volume levels used to send each channel of this voice's output audio to each channel of a given destination voice's input audio. + // ARGUMENTS: + // pDestinationVoice - The destination voice whose mix matrix to obtain. + // SourceChannels - Used to confirm this voice's output channel count (the number of channels produced by the last effect in the chain). + // DestinationChannels - Confirms the destination voice's input channels. + // pLevelMatrix - Array of send levels, as above. + GetOutputMatrix: proc "system" (this: ^IXAudio2Voice, pDestinationVoice: ^IXAudio2Voice, SourceChannels: u32, DestinationChannels: u32, pLevelMatrix: [^]f32), + + // NAME: IXAudio2Voice.DestroyVoice + // DESCRIPTION: Destroys this voice, stopping it if necessary and removing it from the XAudio2 graph. + DestroyVoice: proc "system" (this: ^IXAudio2Voice), +} + +/************************************************************************** + * + * IXAudio2SourceVoice: Source voice management interface. + * + **************************************************************************/ + +IXAudio2SourceVoice :: struct #raw_union { + #subtype ixaudio2voice: IXAudio2Voice, + using ixaudio2sourcevoice_vtable: ^IXAudio2SourceVoice_VTable, +} +IXAudio2SourceVoice_VTable :: struct { + using ixaudio2voice_vtable: IXAudio2Voice_VTable, + + // NAME: IXAudio2SourceVoice.Start + // DESCRIPTION: Makes this voice start consuming and processing audio. + // ARGUMENTS: + // Flags - Flags controlling how the voice should be started. + // OperationSet - Used to identify this call as part of a deferred batch. + Start: proc "system" (this: ^IXAudio2SourceVoice, Flags: FLAGS = {}, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2SourceVoice.Stop + // DESCRIPTION: Makes this voice stop consuming audio. + // ARGUMENTS: + // Flags - Flags controlling how the voice should be stopped. + // OperationSet - Used to identify this call as part of a deferred batch. + Stop: proc "system" (this: ^IXAudio2SourceVoice, Flags: FLAGS = {}, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2SourceVoice.SubmitSourceBuffer + // DESCRIPTION: Adds a new audio buffer to this voice's input queue. + // ARGUMENTS: + // pBuffer - Pointer to the buffer structure to be queued. + // pBufferWMA - Additional structure used only when submitting XWMA data. + SubmitSourceBuffer: proc "system" (this: ^IXAudio2SourceVoice, pBuffer: ^BUFFER, pBufferWMA: ^BUFFER_WMA = nil) -> HRESULT, + + // NAME: IXAudio2SourceVoice.FlushSourceBuffers + // DESCRIPTION: Removes all pending audio buffers from this voice's queue. + FlushSourceBuffers: proc "system" (this: ^IXAudio2SourceVoice) -> HRESULT, + + // NAME: IXAudio2SourceVoice.Discontinuity + // DESCRIPTION: Notifies the voice of an intentional break in the stream of audio buffers (e.g. the end of a sound), to prevent XAudio2 from interpreting an empty buffer queue as a glitch. + Discontinuity: proc "system" (this: ^IXAudio2SourceVoice) -> HRESULT, + + // NAME: IXAudio2SourceVoice.ExitLoop + // DESCRIPTION: Breaks out of the current loop when its end is reached. + // ARGUMENTS: + // OperationSet - Used to identify this call as part of a deferred batch. + ExitLoop: proc "system" (this: ^IXAudio2SourceVoice, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2SourceVoice.GetState + // DESCRIPTION: Returns the number of buffers currently queued on this voice, the pContext value associated with the currently processing buffer (if any), and other voice state information. + // ARGUMENTS: + // pVoiceState - Returns the state information. + // Flags - Flags controlling what voice state is returned. + GetState: proc "system" (this: ^IXAudio2SourceVoice, pVoiceState: ^VOICE_STATE, Flags: FLAGS = {}), + + // NAME: IXAudio2SourceVoice.SetFrequencyRatio + // DESCRIPTION: Sets this voice's frequency adjustment, i.e. its pitch. + // ARGUMENTS: + // Ratio - Frequency change, expressed as source frequency / target frequency. + // OperationSet - Used to identify this call as part of a deferred batch. + SetFrequencyRatio: proc "system" (this: ^IXAudio2SourceVoice, Ratio: f32, OperationSet: u32 = COMMIT_NOW) -> HRESULT, + + // NAME: IXAudio2SourceVoice.GetFrequencyRatio + // DESCRIPTION: Returns this voice's current frequency adjustment ratio. + // ARGUMENTS: + // pRatio - Returns the frequency adjustment. + GetFrequencyRatio: proc "system" (this: ^IXAudio2SourceVoice, pRatio: ^f32), + + // NAME: IXAudio2SourceVoice.SetSourceSampleRate + // DESCRIPTION: Reconfigures this voice to treat its source data as being at a different sample rate than the original one specified in CreateSourceVoice's pSourceFormat argument. + // ARGUMENTS: + // UINT32 - The intended sample rate of further submitted source data. + SetSourceSampleRate: proc "system" (this: ^IXAudio2SourceVoice, NewSourceSampleRate: u32) -> HRESULT, +} + +/************************************************************************** + * + * IXAudio2SubmixVoice: Submixing voice management interface. + * + **************************************************************************/ + +IXAudio2SubmixVoice :: struct #raw_union { + #subtype ixaudio2voice: IXAudio2Voice, + using ixaudio2submixvoice_vtable: ^IXAudio2SubmixVoice_VTable, +} +IXAudio2SubmixVoice_VTable :: struct { + using ixaudio2voice_vtable: IXAudio2Voice_VTable, + // There are currently no methods specific to submix voices. +} + + /************************************************************************** + * + * IXAudio2MasteringVoice: Mastering voice management interface. + * + **************************************************************************/ + +IXAudio2MasteringVoice :: struct #raw_union { + #subtype ixaudio2voice: IXAudio2Voice, + using ixaudio2masteringvoice_vtable: ^IXAudio2MasteringVoice_VTable, +} +IXAudio2MasteringVoice_VTable :: struct { + using ixaudio2voice_vtable: IXAudio2Voice_VTable, + + // NAME: IXAudio2MasteringVoice.GetChannelMask + // DESCRIPTION: Returns the channel mask for this voice + // ARGUMENTS: + // pChannelMask - returns the channel mask for this voice. This corresponds to the dwChannelMask member of WAVEFORMATEXTENSIBLE. + GetChannelMask: proc "system" (this: ^IXAudio2MasteringVoice, pChannelmask: ^win.DWORD) -> HRESULT, +} + +/************************************************************************** + * + * IXAudio2EngineCallback: Client notification interface for engine events. + * + * REMARKS: Contains methods to notify the client when certain events happen in the XAudio2 engine. This interface should be implemented by the client. + * XAudio2 will call these methods via the interface pointer provided by the client when it calls IXAudio2.RegisterForCallbacks. + * + **************************************************************************/ + +IXAudio2EngineCallback :: struct { + using ixaudio2enginecallback_vtable: ^IXAudio2EngineCallback_VTable, +} +IXAudio2EngineCallback_VTable :: struct { + // Called by XAudio2 just before an audio processing pass begins. + OnProcessingPassStart: proc "system" (this: ^IXAudio2EngineCallback), + + // Called just after an audio processing pass ends. + OnProcessingPassEnd: proc "system" (this: ^IXAudio2EngineCallback), + + // Called in the event of a critical system error which requires XAudio2 to be closed down and restarted. The error code is given in Error. + OnCriticalError: proc "system" (this: ^IXAudio2EngineCallback, Error: HRESULT), +} + + /************************************************************************** + * + * IXAudio2VoiceCallback: Client notification interface for voice events. + * + * REMARKS: Contains methods to notify the client when certain events happen in an XAudio2 voice. This interface should be implemented by the client. + * XAudio2 will call these methods via an interface pointer provided by the client in the IXAudio2.CreateSourceVoice call. + * + **************************************************************************/ + +IXAudio2VoiceCallback :: struct { + using ixaudio2voicecallback_vtable: ^IXAudio2VoiceCallback_VTable, +} +IXAudio2VoiceCallback_VTable :: struct { + // Called just before this voice's processing pass begins. + OnVoiceProcessingPassStart: proc "system" (this: ^IXAudio2VoiceCallback, BytesRequired: u32), + + // Called just after this voice's processing pass ends. + OnVoiceProcessingPassEnd: proc "system" (this: ^IXAudio2VoiceCallback), + + // Called when this voice has just finished playing a buffer stream (as marked with the END_OF_STREAM flag on the last buffer). + OnStreamEnd: proc "system" (this: ^IXAudio2VoiceCallback), + + // Called when this voice is about to start processing a new buffer. + OnBufferStart: proc "system" (this: ^IXAudio2VoiceCallback, pBufferContext: rawptr), + + // Called when this voice has just finished processing a buffer. + // The buffer can now be reused or destroyed. + OnBufferEnd: proc "system" (this: ^IXAudio2VoiceCallback, pBufferContext: rawptr), + + // Called when this voice has just reached the end position of a loop. + OnLoopEnd: proc "system" (this: ^IXAudio2VoiceCallback, pBufferContext: rawptr), + + // Called in the event of a critical error during voice processing, such as a failing xAPO or an error from the hardware XMA decoder. + // The voice may have to be destroyed and re-created to recover from the error. + // The callback arguments report which buffer was being processed when the error occurred, and its HRESULT code. + OnVoiceError: proc "system" (this: ^IXAudio2VoiceCallback, pBufferContext: rawptr, Error: HRESULT), +} + +/************************************************************************** + * + * XAudio2Create: Top-level function that creates an XAudio2 instance. + * + * ARGUMENTS: + * + * Flags - Flags specifying the XAudio2 object's behavior. + * + * XAudio2Processor - A PROCESSOR_FLAGS value that specifies the hardware threads (Xbox) or processors (Windows) that XAudio2 will use. + * Note that XAudio2 supports concurrent processing on multiple threads, using any combination of PROCESSOR_FLAGS flags. + * The values are platform-specific; platform-independent code can use USE_DEFAULT_PROCESSOR to use the default on each platform. + * + **************************************************************************/ + +Create :: proc "stdcall" (ppXAudio2: ^^IXAudio2, Flags: FLAGS = {}, XAudio2Processor: PROCESSOR_FLAGS = {.Processor1}) -> HRESULT { + CreateWithVersionInfoFunc :: #type proc "c" (a0: ^^IXAudio2, a1: FLAGS, a2: PROCESSOR_FLAGS, a3: win.DWORD) -> HRESULT + CreateInfoFunc :: #type proc "c" (a0: ^^IXAudio2, a1: FLAGS, a2: PROCESSOR_FLAGS) -> HRESULT + + dll_Instance: win.HMODULE + create_with_version_info: CreateWithVersionInfoFunc + create_info: CreateInfoFunc + + if dll_Instance == nil { + dll_Instance = win.LoadLibraryExW(win.L("xaudio2_9.dll"), nil, {.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS}) + if dll_Instance == nil { + return HRESULT(win.GetLastError()) + } + create_with_version_info = cast(CreateWithVersionInfoFunc)win.GetProcAddress(dll_Instance, "XAudio2CreateWithVersionInfo") + if create_with_version_info == nil { + create_info = cast(CreateInfoFunc)win.GetProcAddress(dll_Instance, "XAudio2Create") + if create_info == nil { + return HRESULT(win.GetLastError()) + } + } + } + if create_with_version_info != nil { + return create_with_version_info(ppXAudio2, Flags, XAudio2Processor, 0x0A000010) + } + return create_info(ppXAudio2, Flags, XAudio2Processor) +} + +/************************************************************************** + * + * Utility functions used to convert from pitch in semitones and volume in decibels to the frequency and amplitude ratio units used by XAudio2. + * + **************************************************************************/ + +// Calculate the argument to SetVolume from a decibel value +DecibelsToAmplitudeRatio :: proc "contextless" (Decibels: f32) -> f32 { + return math.pow_f32(10.0, Decibels / 20.0) +} + +// Recover a volume in decibels from an amplitude factor +AmplitudeRatioToDecibels :: proc "contextless" (Volume: f32) -> f32 { + if Volume == 0 { + return min(f32) + } + return 20.0 * math.log10_f32(Volume) +} + +// Calculate the argument to SetFrequencyRatio from a semitone value +SemitonesToFrequencyRatio :: proc "contextless" (Semitones: f32) -> f32 { + // FrequencyRatio = 2 ^ Octaves + // = 2 ^ (Semitones / 12) + return math.pow_f32(2.0, Semitones / 12.0) +} + +// Recover a pitch in semitones from a frequency ratio +FrequencyRatioToSemitones :: proc "contextless" (FrequencyRatio: f32) -> f32 { + // Semitones = 12 * log2(FrequencyRatio) + // = 12 * log2(10) * log10(FrequencyRatio) + return 12.0 * math.log2_f32(FrequencyRatio) +} + +// Convert from filter cutoff frequencies expressed in Hertz to the radian frequency values used in FILTER_PARAMETERS.Frequency, state-variable filter types only. +// Use CutoffFrequencyToOnePoleCoefficient() for one-pole filter types. +// Note that the highest CutoffFrequency supported is SampleRate/6. +// Higher values of CutoffFrequency will return MAX_FILTER_FREQUENCY. +CutoffFrequencyToRadians :: proc "contextless" (CutoffFrequency: f32, SampleRate: u32) -> f32 { + if u32(CutoffFrequency * 6.0) >= SampleRate { + return MAX_FILTER_FREQUENCY + } + return 2.0 * math.sin_f32(math.PI * CutoffFrequency / f32(SampleRate)) +} + +// Convert from radian frequencies back to absolute frequencies in Hertz +RadiansToCutoffFrequency :: proc "contextless" (Radians: f32, SampleRate: f32) -> f32 { + return SampleRate * math.asin_f32(Radians / 2.0) / math.PI +} + +// Convert from filter cutoff frequencies expressed in Hertz to the filter coefficients used with FILTER_PARAMETERS.Frequency, +// LowPassOnePoleFilter and HighPassOnePoleFilter filter types only. +// Use CutoffFrequencyToRadians() for state-variable filter types. +CutoffFrequencyToOnePoleCoefficient :: proc "contextless" (CutoffFrequency: f32, SampleRate: u32) -> f32 { + if u32(CutoffFrequency) >= SampleRate { + return MAX_FILTER_FREQUENCY + } + return 1.0 - math.pow_f32(1.0 - 2.0 * CutoffFrequency / f32(SampleRate), 2.0) +} + +//------------------------------------------------------------------------- +// Description: Audio stream categories +// +// Other - All other streams (default) +// ForegroundOnlyMedia - (deprecated for Win10) Music, Streaming audio +// BackgroundCapableMedia - (deprecated for Win10) Video with audio +// Communications - VOIP, chat, phone call +// Alerts - Alarm, Ring tones +// SoundEffects - Sound effects, clicks, dings +// GameEffects - Game sound effects +// GameMedia - Background audio for games +// GameChat - In game player chat +// Speech - Speech recognition +// Media - Music, Streaming audio +// Movie - Video with audio +// FarFieldSpeech - Capture of far field speech +// UniformSpeech - Uniform, device agnostic speech processing +// VoiceTyping - Dictation, typing by voice +// +AUDIO_STREAM_CATEGORY :: enum i32 { + Other = 0, + //ForegroundOnlyMedia = 1, + //BackgroundCapableMedia = 2, + Communications = 3, + Alerts = 4, + SoundEffects = 5, + GameEffects = 6, + GameMedia = 7, + GameChat = 8, + Speech = 9, + Movie = 10, + Media = 11, + FarFieldSpeech = 12, + UniformSpeech = 13, + VoiceTyping = 14, +} diff --git a/vendor/windows/XAudio2/xaudio2fx.odin b/vendor/windows/XAudio2/xaudio2fx.odin new file mode 100644 index 000000000..1449ed4ea --- /dev/null +++ b/vendor/windows/XAudio2/xaudio2fx.odin @@ -0,0 +1,282 @@ +#+build windows + +package windows_xaudio2 + +import "core:math" + +foreign import xa2 "system:xaudio2.lib" + +/************************************************************************** + * + * Effect creation functions. + * + * On Xbox the application can link with the debug library to use the debug + * functionality. + * + **************************************************************************/ + +@(default_calling_convention="system") +foreign xa2 { + CreateAudioVolumeMeter :: proc(ppApo: ^^IUnknown) -> HRESULT --- + CreateAudioReverb :: proc(ppApo: ^^IUnknown) -> HRESULT --- +} + +/************************************************************************** + * + * Volume meter parameters. + * The volume meter supports f32 audio formats and must be used in-place. + * + **************************************************************************/ + +// VOLUMEMETER_LEVELS: Receives results from GetEffectParameters(). +// The user is responsible for allocating pPeakLevels, pRMSLevels, and initializing ChannelCount accordingly. +// The volume meter does not support SetEffectParameters(). +VOLUMEMETER_LEVELS :: struct #packed { + pPeakLevels: [^]f32 `fmt:"v,ChannelCount"`, // Peak levels table: receives maximum absolute level for each channel over a processing pass, may be NULL if pRMSLevls != NULL, otherwise must have at least ChannelCount elements. + pRMSLevels: [^]f32 `fmt:"v,ChannelCount"`, // Root mean square levels table: receives RMS level for each channel over a processing pass, may be NULL if pPeakLevels != NULL, otherwise must have at least ChannelCount elements. + ChannelCount: u32, // Number of channels being processed by the volume meter APO +} + +/************************************************************************** + * + * Reverb parameters. + * The reverb supports only f32 audio with the following channel configurations: + * Input: Mono Output: Mono + * Input: Mono Output: 5.1 + * Input: Stereo Output: Stereo + * Input: Stereo Output: 5.1 + * The framerate must be within [20000, 48000] Hz. + * + * When using mono input, delay filters associated with the right channel are not executed. + * In this case, parameters such as PositionRight and PositionMatrixRight have no effect. + * This also means the reverb uses less CPU when hosted in a mono submix. + * + **************************************************************************/ + +REVERB_MIN_FRAMERATE :: 20000 +REVERB_MAX_FRAMERATE :: 48000 + +// REVERB_PARAMETERS: Native parameter set for the reverb effect + +REVERB_PARAMETERS :: struct #packed { + // ratio of wet (processed) signal to dry (original) signal + WetDryMix: f32, // [0, 100] (percentage) + // Delay times + ReflectionsDelay: u32, // [0, 300] in ms + ReverbDelay: byte, // [0, 85] in ms + RearDelay: byte, // 7.1: [0, 20] in ms, all other: [0, 5] in ms + SideDelay: byte, // 7.1: [0, 5] in ms, all other: not used, but still validated + // Indexed parameters + PositionLeft: byte, // [0, 30] no units + PositionRight: byte, // [0, 30] no units, ignored when configured to mono + PositionMatrixLeft: byte, // [0, 30] no units + PositionMatrixRight: byte, // [0, 30] no units, ignored when configured to mono + EarlyDiffusion: byte, // [0, 15] no units + LateDiffusion: byte, // [0, 15] no units + LowEQGain: byte, // [0, 12] no units + LowEQCutoff: byte, // [0, 9] no units + HighEQGain: byte, // [0, 8] no units + HighEQCutoff: byte, // [0, 14] no units + // Direct parameters + RoomFilterFreq: f32, // [20, 20000] in Hz + RoomFilterMain: f32, // [-100, 0] in dB + RoomFilterHF: f32, // [-100, 0] in dB + ReflectionsGain: f32, // [-100, 20] in dB + ReverbGain: f32, // [-100, 20] in dB + DecayTime: f32, // [0.1, inf] in seconds + Density: f32, // [0, 100] (percentage) + RoomSize: f32, // [1, 100] in feet + // component control + DisableLateField: b32, // TRUE to disable late field reflections +} + +// Maximum, minimum and default values for the parameters above +REVERB_MIN_WET_DRY_MIX :: 0.0 +REVERB_MIN_REFLECTIONS_DELAY :: 0 +REVERB_MIN_REVERB_DELAY :: 0 +REVERB_MIN_REAR_DELAY :: 0 +REVERB_MIN_7POINT1_SIDE_DELAY :: 0 +REVERB_MIN_7POINT1_REAR_DELAY :: 0 +REVERB_MIN_POSITION :: 0 +REVERB_MIN_DIFFUSION :: 0 +REVERB_MIN_LOW_EQ_GAIN :: 0 +REVERB_MIN_LOW_EQ_CUTOFF :: 0 +REVERB_MIN_HIGH_EQ_GAIN :: 0 +REVERB_MIN_HIGH_EQ_CUTOFF :: 0 +REVERB_MIN_ROOM_FILTER_FREQ :: 20.0 +REVERB_MIN_ROOM_FILTER_MAIN :: -100.0 +REVERB_MIN_ROOM_FILTER_HF :: -100.0 +REVERB_MIN_REFLECTIONS_GAIN :: -100.0 +REVERB_MIN_REVERB_GAIN :: -100.0 +REVERB_MIN_DECAY_TIME :: 0.1 +REVERB_MIN_DENSITY :: 0.0 +REVERB_MIN_ROOM_SIZE :: 0.0 + +REVERB_MAX_WET_DRY_MIX :: 100.0 +REVERB_MAX_REFLECTIONS_DELAY :: 300 +REVERB_MAX_REVERB_DELAY :: 85 +REVERB_MAX_REAR_DELAY :: 5 +REVERB_MAX_7POINT1_SIDE_DELAY :: 5 +REVERB_MAX_7POINT1_REAR_DELAY :: 20 +REVERB_MAX_POSITION :: 30 +REVERB_MAX_DIFFUSION :: 15 +REVERB_MAX_LOW_EQ_GAIN :: 12 +REVERB_MAX_LOW_EQ_CUTOFF :: 9 +REVERB_MAX_HIGH_EQ_GAIN :: 8 +REVERB_MAX_HIGH_EQ_CUTOFF :: 14 +REVERB_MAX_ROOM_FILTER_FREQ :: 20000.0 +REVERB_MAX_ROOM_FILTER_MAIN :: 0.0 +REVERB_MAX_ROOM_FILTER_HF :: 0.0 +REVERB_MAX_REFLECTIONS_GAIN :: 20.0 +REVERB_MAX_REVERB_GAIN :: 20.0 +REVERB_MAX_DENSITY :: 100.0 +REVERB_MAX_ROOM_SIZE :: 100.0 + +REVERB_DEFAULT_WET_DRY_MIX :: 100.0 +REVERB_DEFAULT_REFLECTIONS_DELAY :: 5 +REVERB_DEFAULT_REVERB_DELAY :: 5 +REVERB_DEFAULT_REAR_DELAY :: 5 +REVERB_DEFAULT_7POINT1_SIDE_DELAY :: 5 +REVERB_DEFAULT_7POINT1_REAR_DELAY :: 20 +REVERB_DEFAULT_POSITION :: 6 +REVERB_DEFAULT_POSITION_MATRIX :: 27 +REVERB_DEFAULT_EARLY_DIFFUSION :: 8 +REVERB_DEFAULT_LATE_DIFFUSION :: 8 +REVERB_DEFAULT_LOW_EQ_GAIN :: 8 +REVERB_DEFAULT_LOW_EQ_CUTOFF :: 4 +REVERB_DEFAULT_HIGH_EQ_GAIN :: 8 +REVERB_DEFAULT_HIGH_EQ_CUTOFF :: 4 +REVERB_DEFAULT_ROOM_FILTER_FREQ :: 5000.0 +REVERB_DEFAULT_ROOM_FILTER_MAIN :: 0.0 +REVERB_DEFAULT_ROOM_FILTER_HF :: 0.0 +REVERB_DEFAULT_REFLECTIONS_GAIN :: 0.0 +REVERB_DEFAULT_REVERB_GAIN :: 0.0 +REVERB_DEFAULT_DECAY_TIME :: 1.0 +REVERB_DEFAULT_DENSITY :: 100.0 +REVERB_DEFAULT_ROOM_SIZE :: 100.0 + +REVERB_DEFAULT_DISABLE_LATE_FIELD: b32 : false + +// REVERB_I3DL2_PARAMETERS: Parameter set compliant with the I3DL2 standard + +REVERB_I3DL2_PARAMETERS :: struct #packed { + // ratio of wet (processed) signal to dry (original) signal + WetDryMix: f32, // [0, 100] (percentage) + + // Standard I3DL2 parameters + Room: i32, // [-10000, 0] in mB (hundredths of decibels) + RoomHF: i32, // [-10000, 0] in mB (hundredths of decibels) + RoomRolloffFactor: f32, // [0.0, 10.0] + DecayTime: f32, // [0.1, 20.0] in seconds + DecayHFRatio: f32, // [0.1, 2.0] + Reflections: i32, // [-10000, 1000] in mB (hundredths of decibels) + ReflectionsDelay: f32, // [0.0, 0.3] in seconds + Reverb: i32, // [-10000, 2000] in mB (hundredths of decibels) + ReverbDelay: f32, // [0.0, 0.1] in seconds + Diffusion: f32, // [0.0, 100.0] (percentage) + Density: f32, // [0.0, 100.0] (percentage) + HFReference: f32, // [20.0, 20000.0] in Hz +} + +/************************************************************************** + * + * Standard I3DL2 reverb presets (100% wet). + * + **************************************************************************/ + +I3DL2_PRESET_DEFAULT := REVERB_I3DL2_PARAMETERS{100.0,-10000, 0,0.0, 1.00,0.50,-10000,0.020,-10000,0.040,100.0,100.0,5000.0} +I3DL2_PRESET_GENERIC := REVERB_I3DL2_PARAMETERS{100.0, -1000, -100,0.0, 1.49,0.83, -2602,0.007, 200,0.011,100.0,100.0,5000.0} +I3DL2_PRESET_PADDEDCELL := REVERB_I3DL2_PARAMETERS{100.0, -1000,-6000,0.0, 0.17,0.10, -1204,0.001, 207,0.002,100.0,100.0,5000.0} +I3DL2_PRESET_ROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000, -454,0.0, 0.40,0.83, -1646,0.002, 53,0.003,100.0,100.0,5000.0} +I3DL2_PRESET_BATHROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000,-1200,0.0, 1.49,0.54, -370,0.007, 1030,0.011,100.0, 60.0,5000.0} +I3DL2_PRESET_LIVINGROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000,-6000,0.0, 0.50,0.10, -1376,0.003, -1104,0.004,100.0,100.0,5000.0} +I3DL2_PRESET_STONEROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000, -300,0.0, 2.31,0.64, -711,0.012, 83,0.017,100.0,100.0,5000.0} +I3DL2_PRESET_AUDITORIUM := REVERB_I3DL2_PARAMETERS{100.0, -1000, -476,0.0, 4.32,0.59, -789,0.020, -289,0.030,100.0,100.0,5000.0} +I3DL2_PRESET_CONCERTHALL := REVERB_I3DL2_PARAMETERS{100.0, -1000, -500,0.0, 3.92,0.70, -1230,0.020, -2,0.029,100.0,100.0,5000.0} +I3DL2_PRESET_CAVE := REVERB_I3DL2_PARAMETERS{100.0, -1000, 0,0.0, 2.91,1.30, -602,0.015, -302,0.022,100.0,100.0,5000.0} +I3DL2_PRESET_ARENA := REVERB_I3DL2_PARAMETERS{100.0, -1000, -698,0.0, 7.24,0.33, -1166,0.020, 16,0.030,100.0,100.0,5000.0} +I3DL2_PRESET_HANGAR := REVERB_I3DL2_PARAMETERS{100.0, -1000,-1000,0.0,10.05,0.23, -602,0.020, 198,0.030,100.0,100.0,5000.0} +I3DL2_PRESET_CARPETEDHALLWAY := REVERB_I3DL2_PARAMETERS{100.0, -1000,-4000,0.0, 0.30,0.10, -1831,0.002, -1630,0.030,100.0,100.0,5000.0} +I3DL2_PRESET_HALLWAY := REVERB_I3DL2_PARAMETERS{100.0, -1000, -300,0.0, 1.49,0.59, -1219,0.007, 441,0.011,100.0,100.0,5000.0} +I3DL2_PRESET_STONECORRIDOR := REVERB_I3DL2_PARAMETERS{100.0, -1000, -237,0.0, 2.70,0.79, -1214,0.013, 395,0.020,100.0,100.0,5000.0} +I3DL2_PRESET_ALLEY := REVERB_I3DL2_PARAMETERS{100.0, -1000, -270,0.0, 1.49,0.86, -1204,0.007, -4,0.011,100.0,100.0,5000.0} +I3DL2_PRESET_FOREST := REVERB_I3DL2_PARAMETERS{100.0, -1000,-3300,0.0, 1.49,0.54, -2560,0.162, -613,0.088, 79.0,100.0,5000.0} +I3DL2_PRESET_CITY := REVERB_I3DL2_PARAMETERS{100.0, -1000, -800,0.0, 1.49,0.67, -2273,0.007, -2217,0.011, 50.0,100.0,5000.0} +I3DL2_PRESET_MOUNTAINS := REVERB_I3DL2_PARAMETERS{100.0, -1000,-2500,0.0, 1.49,0.21, -2780,0.300, -2014,0.100, 27.0,100.0,5000.0} +I3DL2_PRESET_QUARRY := REVERB_I3DL2_PARAMETERS{100.0, -1000,-1000,0.0, 1.49,0.83,-10000,0.061, 500,0.025,100.0,100.0,5000.0} +I3DL2_PRESET_PLAIN := REVERB_I3DL2_PARAMETERS{100.0, -1000,-2000,0.0, 1.49,0.50, -2466,0.179, -2514,0.100, 21.0,100.0,5000.0} +I3DL2_PRESET_PARKINGLOT := REVERB_I3DL2_PARAMETERS{100.0, -1000, 0,0.0, 1.65,1.50, -1363,0.008, -1153,0.012,100.0,100.0,5000.0} +I3DL2_PRESET_SEWERPIPE := REVERB_I3DL2_PARAMETERS{100.0, -1000,-1000,0.0, 2.81,0.14, 429,0.014, 648,0.021, 80.0, 60.0,5000.0} +I3DL2_PRESET_UNDERWATER := REVERB_I3DL2_PARAMETERS{100.0, -1000,-4000,0.0, 1.49,0.10, -449,0.007, 1700,0.011,100.0,100.0,5000.0} +I3DL2_PRESET_SMALLROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000, -600,0.0, 1.10,0.83, -400,0.005, 500,0.010,100.0,100.0,5000.0} +I3DL2_PRESET_MEDIUMROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000, -600,0.0, 1.30,0.83, -1000,0.010, -200,0.020,100.0,100.0,5000.0} +I3DL2_PRESET_LARGEROOM := REVERB_I3DL2_PARAMETERS{100.0, -1000, -600,0.0, 1.50,0.83, -1600,0.020, -1000,0.040,100.0,100.0,5000.0} +I3DL2_PRESET_MEDIUMHALL := REVERB_I3DL2_PARAMETERS{100.0, -1000, -600,0.0, 1.80,0.70, -1300,0.015, -800,0.030,100.0,100.0,5000.0} +I3DL2_PRESET_LARGEHALL := REVERB_I3DL2_PARAMETERS{100.0, -1000, -600,0.0, 1.80,0.70, -2000,0.030, -1400,0.060,100.0,100.0,5000.0} +I3DL2_PRESET_PLATE := REVERB_I3DL2_PARAMETERS{100.0, -1000, -200,0.0, 1.30,0.90, 0,0.002, 0,0.010,100.0, 75.0,5000.0} + +// ReverbConvertI3DL2ToNative: Utility function to map from I3DL2 to native parameters + +ReverbConvertI3DL2ToNative :: proc "contextless" (pI3DL2: ^REVERB_I3DL2_PARAMETERS, pNative: ^REVERB_PARAMETERS, sevenDotOneReverb: b32 = true) { + reflectionsDelay: f32 + reverbDelay: f32 + + // RoomRolloffFactor is ignored + + // These parameters have no equivalent in I3DL2 + if sevenDotOneReverb { + pNative.RearDelay = REVERB_DEFAULT_7POINT1_REAR_DELAY // 20 + } else { + pNative.RearDelay = REVERB_DEFAULT_REAR_DELAY // 5 + } + pNative.SideDelay = REVERB_DEFAULT_7POINT1_SIDE_DELAY // 5 + pNative.PositionLeft = REVERB_DEFAULT_POSITION // 6 + pNative.PositionRight = REVERB_DEFAULT_POSITION // 6 + pNative.PositionMatrixLeft = REVERB_DEFAULT_POSITION_MATRIX // 27 + pNative.PositionMatrixRight = REVERB_DEFAULT_POSITION_MATRIX // 27 + pNative.RoomSize = REVERB_DEFAULT_ROOM_SIZE // 100 + pNative.LowEQCutoff = 4 + pNative.HighEQCutoff = 6 + + // The rest of the I3DL2 parameters map to the native property set + pNative.RoomFilterMain = f32(pI3DL2.Room) / 100.0 + pNative.RoomFilterHF = f32(pI3DL2.RoomHF) / 100.0 + + if pI3DL2.DecayHFRatio >= 1.0 { + index := i32(-4.0 * math.log10_f32(pI3DL2.DecayHFRatio)) + if index < -8 { index = -8 } + pNative.LowEQGain = byte((index < 0) ? index + 8 : 8) + pNative.HighEQGain = 8 + pNative.DecayTime = pI3DL2.DecayTime * pI3DL2.DecayHFRatio + } else { + index := i32(4.0 * math.log10_f32(pI3DL2.DecayHFRatio)) + if index < -8 { index = -8 } + pNative.LowEQGain = 8 + pNative.HighEQGain = byte((index < 0) ? index + 8 : 8) + pNative.DecayTime = pI3DL2.DecayTime + } + + reflectionsDelay = pI3DL2.ReflectionsDelay * 1000.0 + if reflectionsDelay >= REVERB_MAX_REFLECTIONS_DELAY { // 300 + reflectionsDelay = f32(REVERB_MAX_REFLECTIONS_DELAY - 1) + } else if reflectionsDelay <= 1 { + reflectionsDelay = 1 + } + pNative.ReflectionsDelay = u32(reflectionsDelay) + + reverbDelay = pI3DL2.ReverbDelay * 1000.0 + if reverbDelay >= REVERB_MAX_REVERB_DELAY { // 85 + reverbDelay = f32(REVERB_MAX_REVERB_DELAY - 1) + } + pNative.ReverbDelay = byte(reverbDelay) + + pNative.ReflectionsGain = f32(pI3DL2.Reflections) / 100.0 + pNative.ReverbGain = f32(pI3DL2.Reverb) / 100.0 + pNative.EarlyDiffusion = byte(15.0 * pI3DL2.Diffusion / 100.0) + pNative.LateDiffusion = pNative.EarlyDiffusion + pNative.Density = pI3DL2.Density + pNative.RoomFilterFreq = pI3DL2.HFReference + + pNative.WetDryMix = pI3DL2.WetDryMix + pNative.DisableLateField = false +} diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 2a8d6832b..2cd4e0f83 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -234,7 +234,7 @@ foreign xlib { display: ^Display, window: Window, attr_mask: WindowAttributeMask, - attr: XWindowAttributes, + attr: ^XWindowAttributes, ) --- SetWindowBackground :: proc( display: ^Display,