mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-04-24 10:28:46 +02:00
Add DOS platform support (DJGPP) (#15377)
* dos: Some initial work. * dos: Turn off buffer on stdio SDL_IOStreams. Seeking breaks otherwise. We might be able to just fflush() before or seeking instead? * dos: Audio implementation using the Sound Blaster 16. * dos: remove audio Pump interface. Turns out DosBox-X was having trouble with the Sound Blaster or something; standard DosBox works correctly directly from the interrupt handler, and without doubling the buffer size. * dos: just dump and restore the stdio buffer when seeking. This is MUCH faster than just leaving buffering disabled, and also works around getting bogus reads after an fseek. SDL_LoadWAV on test/sample.wav no longer takes several seconds to finish, and comes up with the correct data. I wonder if we're triggering this in LoadWAV because we're malloc'ing data between seeks/reads, and it's causing the djgpp transfer buffer to change. Or maybe the Fat DS trick is confusing it? I don't know, I haven't had time to debug it, it might just be a legit libc bug in djgpp too, for all I know. * dos: Protect audio device "thread" iterations when streams are locked. This uses an old trick we used in SDL 1.2 for MacOS Classic, which did its audio callback in a hardware interrupt. If the audio is locked when the interrupt fires, make a note of it and return immediately. When the lock is released, if the interrupt has been fired, run the audio device iteration right then. Since there isn't a big device lock in SDL3 (available to the app, at least), this keeps a counter of when any SDL_AudioStream is locked, which is probably good enough. * dos: Implemented initial video subsystem. This uses VESA interfaces to manage the display and works with the software renderer. Events aren't hooked up yet, so prepare to close DosBox on each run. :) * dos: Whoops, forgot to add these to revision control. Core and Main support. * dos: Wired up basic filesystem support. This gets most of the rendering examples, which use SDL_GetBasePath() to find textures to load, working. * dos: Fixed compiler warning. * dos: Initial mouse support! * dos: Move interrupt hooking code into core/dos. * dos: Initial keyboard support! * dos: Use a simple ring buffer for keyboard events. Of course Quake 1 solved this better, haha. It's smart: less memory, dirt simple, and you don't even have to worry about synchronizing with the interrupt handler, because it's safe for both sides no matter when an interrupt fires. * ci: add djgpp job [sdl-ci-filter djgpp] [sdl-ci-artifacts] * dos: Fix build issues after rebase onto current main - SDL_runapp.c: Add SDL_PLATFORM_DOS to the exclusion list so the generic SDL_RunApp() is disabled when the DOS-specific one is compiled. - SDL.c: Exclude SDL_Gtk_Quit() on DOS. DJGPP defines __unix__ which sets SDL_PLATFORM_UNIX, but DOS has no GTK/display server. The GTK source is not compiled (CMake UNIX is false for DOS) so this was a link error. - sdlplatform.cmake: Add DOS case to SDL_DetectCMakePlatform so the platform is properly detected from CMAKE_SYSTEM_NAME=DOS. - i586-pc-msdosdjgpp.cmake: Add i386-pc-msdosdjgpp-gcc as a fallback compiler name, since some DJGPP toolchain builds use the i386 prefix. * Add 8-bit palette support to DOS VESA driver * Add VBE page-flipping, state restore, and robust keyboard handling - Implement double-buffered page-flipping for VBE modes with >1 image page - Save and restore full VBE state on video init/quit for clean mode switching - Improve DOS keyboard handling: support extended scancodes and Pause key - Lock ISR code/data to prevent page faults during interrupts - Always vsync when blitting in single-buffered modes to reduce tearing * Refactor Sound Blaster audio mixing to main loop Move audio mixing out of IRQ handler to main loop for improved stability and to avoid reentrancy issues. Add SDL_DOS_PumpAudio function, update DMA buffer handling, and adjust sample rate to 22050 Hz. Silence stale DMA buffer halves to prevent stutter during load. * Add DOS timer support and update build config * Add support for pre-SB16 8-bit mono Sound Blaster audio Detect SB version and select 8-bit mono or 16-bit stereo mode. Handle DMA and DSP setup for both SB16 and pre-SB16 hardware. Add FORCE_SB_8BIT option for testing in DOSBox. * Add SB Pro stereo support and simplify IRQ handler * Add DOS joystick driver support * Improve DOS hardware handling and clarify memory allocation - Poll Sound Blaster DSP status instead of fixed delay after speaker-on - Clarify DPMI conventional memory is always locked; update comments - Document and justify DMA memory allocation strategy - Free IRET wrapper after restoring interrupt vector to avoid leaks - Throttle joystick axis polling to ~60 Hz to reduce BIOS timing loop cost - Always poll joystick buttons directly for responsiveness * Query and use mouse sensitivity from INT 33h function 0x1B * Add support for VESA banked framebuffer modes Implement banked framebuffer access for VBE 1.2+ modes without LFB. Detect and initialize banked modes, copy framebuffer data using bank switching, and blank the framebuffer on mode set. Page-flipping is disabled in banked mode. * Add optional vsync to page flipping in DOS VESA driver * Add cooperative threading support for DOS platform * Move SoundBlaster audio mixing to SDL audio thread * Fix DOS platform comments and workarounds for DJGPP support * Fix SoundBlaster IRQ handling and DMA setup for DOS - Pass IRQ number to DOS_EndOfInterrupt and handle slave PIC EOI - Validate DMA channel from BLASTER variable - Correct DMA page register selection for SB16 - Improve BLASTER variable parsing and error messages - Unmask/mask IRQs on correct PIC in DOS_HookInterrupt - Rename SDL_dosjoystick.c to SDL_sysjoystick.c - Include SDL_main_callbacks.h in SDL_sysmain_runapp.c - Add include guard to SDL_systhread_c.h * Add DOS platform options and preseed cache for DJGPP Disable unsupported SDL features when building for DOS. Add PreseedDOSCache.cmake to pre-populate CMake cache variables for DJGPP. * cmake: use a 8.3 naming scheme for tests on DOS * Apply code style * Update include/SDL3/SDL_platform_defines.h Co-authored-by: Anonymous Maarten <madebr@users.noreply.github.com> * Code review clean up - Split DOS VESA mode-setting into its own file - Replace magic numbers with named constants - Update copyright dates to 2026 - Substract time taken by other threads form delays * Fix DOS bugs and improve compatibility - Disable fseeko64 for DJGPP due to broken implementation - Refactor DOS timer delay to always yield and avoid busy-waiting - Fix animated cursor rendering in DOS VESA backend - Always set display mode when creating DOS VESA window - Work around DJGPP allowing invalid file access in testfile.c - Bump max threads to 16 - Apply workarounds for threading tests * Add DOS platform documentation and fix a few issues - Fix fullscreen default resolution - Improve best mode matching - Fix builds on GCC older than 7.0 - Fix text input events * Fix keyboard mapping of "*" * Fix running, and existing, under PCem * Apply suggestions from code review Co-authored-by: Cameron Cawley <ccawley2011@gmail.com> * Pre-mix audio in ring buffer and copy to DMA via IRQ thread * Video fixes and optimizations * DOS: Fix Intel 740 and VGA compatability * DOS: Update readme * DOS: Fix thread ID, get GPU name * DOS: Cap mouse range * DOS: Map test resources to 8.3 names * DOS: Skip unsupported WM color modes * Fix "windowed" resolution selection * DOS: Hide INDEX8 modes behind SDL_DOS_ALLOW_INDEX8_MODES * Remove SDL_HINT_DOS_ALLOW_INDEX8_MODES and order modes logically * Don't convert cursor if dest is not INDEX8 --------- Co-authored-by: Ryan C. Gordon <icculus@icculus.org> Co-authored-by: Anonymous Maarten <anonymous.maarten@gmail.com> Co-authored-by: Cameron Cawley <ccawley2011@gmail.com> Co-authored-by: Gleb Mazovetskiy <glex.spb@gmail.com> Co-authored-by: Jay Petacat <jay@jayschwa.net> Tested-by: Cameron Cawley <ccawley2011@gmail.com>
This commit is contained in:
66
.github/actions/setup-djgpp-toolchain/action.yml
vendored
Normal file
66
.github/actions/setup-djgpp-toolchain/action.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: 'Setup DJGPP toolchain'
|
||||
description: 'Download DJGPP and setup CMake toolchain'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Calculate variables'
|
||||
id: calc
|
||||
shell: sh
|
||||
run: |
|
||||
version="12.2.0"
|
||||
case "${{ runner.os }}-${{ runner.arch }}" in
|
||||
"Linux-X86")
|
||||
archive="djgpp-linux32-gcc1220.tar.bz2"
|
||||
;;
|
||||
"Linux-X64")
|
||||
archive="djgpp-linux64-gcc1220.tar.bz2"
|
||||
;;
|
||||
"macOS-X86" | "macOS-X64" | "macOS-ARM64")
|
||||
archive="djgpp-osx-gcc1220.tar.bz2"
|
||||
;;
|
||||
"Windows-X86" | "Windows-X64")
|
||||
archive="djgpp-mingw-gcc1220.zip"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported ${{ runner.os }}-${{ runner.arch }}"
|
||||
exit 1;
|
||||
;;
|
||||
esac
|
||||
echo "url=https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/${archive}" >> ${GITHUB_OUTPUT}
|
||||
echo "archive=${archive}" >> ${GITHUB_OUTPUT}
|
||||
echo "version=${version}" >> ${GITHUB_OUTPUT}
|
||||
echo "cache-key=${archive}-${{ inputs.version }}-${{ runner.os }}-${{ runner.arch }}" >> ${GITHUB_OUTPUT}
|
||||
- name: 'Restore cached ${{ steps.calc.outputs.archive }}'
|
||||
id: cache-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}'
|
||||
key: ${{ steps.calc.outputs.cache-key }}
|
||||
- name: 'Download DJGPP ${{ steps.calc.outputs.version }} for ${{ runner.os }} (${{ runner.arch }})'
|
||||
if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Invoke-WebRequest "${{ steps.calc.outputs.url }}" -OutFile "${{ runner.temp }}/${{ steps.calc.outputs.archive }}"
|
||||
- name: 'Cache ${{ steps.calc.outputs.archive }}'
|
||||
if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}'
|
||||
key: ${{ steps.calc.outputs.cache-key }}
|
||||
- name: 'Extract DJGP archive'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$archive = "${{ steps.calc.outputs.archive }}";
|
||||
if ($archive.EndsWith(".bz2")) {
|
||||
# Remove ".bz2" suffix
|
||||
$tar_archive = $archive.Substring(0, $archive.Length - 4)
|
||||
7z "-o${{ runner.temp }}" x "${{ runner.temp }}/${{ steps.calc.outputs.archive }}"
|
||||
7z "-o${{ runner.temp }}" x "${{ runner.temp }}/$tar_archive"
|
||||
} else {
|
||||
7z "-o${{ runner.temp }}" x "${{ runner.temp }}/${{ steps.calc.outputs.archive }}"
|
||||
}
|
||||
- name: 'Set output variables'
|
||||
id: final
|
||||
shell: pwsh
|
||||
run: |
|
||||
echo "${{ runner.temp }}/djgpp/bin" >> $env:GITHUB_PATH
|
||||
16
.github/workflows/create-test-plan.py
vendored
16
.github/workflows/create-test-plan.py
vendored
@@ -58,6 +58,7 @@ class SdlPlatform(Enum):
|
||||
NetBSD = "netbsd"
|
||||
OpenBSD = "openbsd"
|
||||
NGage = "ngage"
|
||||
DJGPP = "djgpp"
|
||||
|
||||
|
||||
class Msys2Platform(Enum):
|
||||
@@ -149,6 +150,7 @@ JOB_SPECS = {
|
||||
"openbsd": JobSpec(name="OpenBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.OpenBSD, artifact="SDL-openbsd-x64", ),
|
||||
"freebsd": JobSpec(name="FreeBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.FreeBSD, artifact="SDL-freebsd-x64", ),
|
||||
"ngage": JobSpec(name="N-Gage", os=JobOs.WindowsLatest, platform=SdlPlatform.NGage, artifact="SDL-ngage", ),
|
||||
"djgpp": JobSpec(name="DOS (DJGPP)", os=JobOs.UbuntuLatest, platform=SdlPlatform.DJGPP, artifact="SDL-djgpp", ),
|
||||
}
|
||||
|
||||
|
||||
@@ -821,6 +823,20 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args
|
||||
job.setup_gage_sdk_path = "C:/ngagesdk"
|
||||
job.cmake_toolchain_file = "C:/ngagesdk/cmake/ngage-toolchain.cmake"
|
||||
job.test_pkg_config = False
|
||||
case SdlPlatform.DJGPP:
|
||||
build_parallel = False
|
||||
job.ccache = True
|
||||
job.apt_packages = ["ccache", "libfl-dev"] # djgpp needs libfl.so.2
|
||||
job.cmake_build_type = "Release"
|
||||
job.setup_ninja = True
|
||||
job.static_lib = StaticLibType.A
|
||||
job.shared_lib = None
|
||||
job.clang_tidy = False
|
||||
job.werror = False # FIXME: enable SDL_WERROR
|
||||
job.shared = False
|
||||
job.run_tests = False
|
||||
job.test_pkg_config = False
|
||||
job.cmake_toolchain_file = "$GITHUB_WORKSPACE/build-scripts/i586-pc-msdosdjgpp.cmake"
|
||||
case _:
|
||||
raise ValueError(f"Unsupported platform={spec.platform}")
|
||||
|
||||
|
||||
4
.github/workflows/generic.yml
vendored
4
.github/workflows/generic.yml
vendored
@@ -100,6 +100,10 @@ jobs:
|
||||
uses: ./.github/actions/setup-loongarch64-toolchain
|
||||
id: setup-loongarch64-toolchain
|
||||
if: ${{ matrix.platform.platform == 'loongarch64' }}
|
||||
- name: 'Set up DJGPP toolchain'
|
||||
uses: ./.github/actions/setup-djgpp-toolchain
|
||||
id: setup-djgpp-toolchain
|
||||
if: ${{ matrix.platform.platform == 'djgpp' }}
|
||||
- name: 'Setup Intel oneAPI toolchain'
|
||||
id: intel
|
||||
if: ${{ matrix.platform.intel }}
|
||||
|
||||
@@ -78,6 +78,7 @@ include("${SDL3_SOURCE_DIR}/cmake/3rdparty.cmake")
|
||||
include("${SDL3_SOURCE_DIR}/cmake/PreseedMSVCCache.cmake")
|
||||
include("${SDL3_SOURCE_DIR}/cmake/PreseedEmscriptenCache.cmake")
|
||||
include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake")
|
||||
include("${SDL3_SOURCE_DIR}/cmake/PreseedDOSCache.cmake")
|
||||
|
||||
SDL_DetectCompiler()
|
||||
SDL_DetectTargetCPUArchitectures(SDL_CPUS)
|
||||
@@ -163,7 +164,7 @@ endif()
|
||||
# The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers,
|
||||
# so we'll just use libusb when it's available. libusb does not support iOS,
|
||||
# so we default to yes on iOS.
|
||||
if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID OR NGAGE)
|
||||
if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID OR NGAGE OR DOS)
|
||||
set(SDL_HIDAPI_LIBUSB_AVAILABLE FALSE)
|
||||
else()
|
||||
set(SDL_HIDAPI_LIBUSB_AVAILABLE TRUE)
|
||||
@@ -207,7 +208,7 @@ if(EMSCRIPTEN)
|
||||
set(SDL_SHARED_AVAILABLE OFF)
|
||||
endif()
|
||||
|
||||
if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE)
|
||||
if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE OR DOS)
|
||||
set(SDL_SHARED_AVAILABLE OFF)
|
||||
endif()
|
||||
|
||||
@@ -425,6 +426,21 @@ if(VITA)
|
||||
set_option(VIDEO_VITA_PVR "Build with PSVita PVR gles/gles2 support" OFF)
|
||||
endif()
|
||||
|
||||
if(DOS)
|
||||
set(SDL_GPU OFF)
|
||||
set(SDL_CAMERA OFF)
|
||||
set(SDL_HAPTIC OFF)
|
||||
set(SDL_HIDAPI OFF)
|
||||
set(SDL_POWER OFF)
|
||||
set(SDL_SENSOR OFF)
|
||||
set(SDL_DIALOG OFF)
|
||||
set(SDL_DUMMYCAMERA OFF)
|
||||
set(SDL_OFFSCREEN OFF)
|
||||
set(SDL_RENDER_GPU OFF)
|
||||
set(SDL_TRAY OFF)
|
||||
set(SDL_PROCESS OFF)
|
||||
endif()
|
||||
|
||||
if (NGAGE)
|
||||
set(SDL_GPU OFF)
|
||||
set(SDL_CAMERA OFF)
|
||||
@@ -3332,6 +3348,51 @@ elseif(N3DS)
|
||||
"${SDL3_SOURCE_DIR}/src/io/n3ds/*.h"
|
||||
)
|
||||
|
||||
elseif(DOS)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/dos/*.c")
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/dos/*.c")
|
||||
|
||||
set(SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/dos/*.c")
|
||||
set(HAVE_SDL_AUDIO TRUE)
|
||||
|
||||
set(SDL_VIDEO_DRIVER_DOSVESA 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/dos/*.c")
|
||||
set(HAVE_SDL_VIDEO TRUE)
|
||||
|
||||
set(SDL_FSOPS_POSIX 1)
|
||||
sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
|
||||
set(HAVE_SDL_FSOPS TRUE)
|
||||
|
||||
set(SDL_FILESYSTEM_DOS 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/dos/*.c")
|
||||
set(HAVE_SDL_FILESYSTEM TRUE)
|
||||
|
||||
# Wall-clock time (SDL_GetDateTimeLocalized etc.) reuses the Unix implementation;
|
||||
# DJGPP provides gettimeofday/localtime so this works as-is.
|
||||
set(SDL_TIME_UNIX 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
|
||||
set(HAVE_SDL_TIME TRUE)
|
||||
|
||||
set(SDL_TIMER_DOS 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/dos/*.c")
|
||||
set(HAVE_SDL_TIMERS TRUE)
|
||||
|
||||
set(SDL_JOYSTICK_DOS 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/dos/*.c")
|
||||
set(HAVE_SDL_JOYSTICK TRUE)
|
||||
|
||||
set(SDL_THREAD_DOS 1)
|
||||
sdl_glob_sources(
|
||||
"${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c"
|
||||
"${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond_c.h"
|
||||
"${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c"
|
||||
"${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock_c.h"
|
||||
"${SDL3_SOURCE_DIR}/src/thread/dos/*.c"
|
||||
"${SDL3_SOURCE_DIR}/src/thread/dos/*.h"
|
||||
)
|
||||
set(HAVE_SDL_THREADS TRUE)
|
||||
|
||||
elseif(NGAGE)
|
||||
|
||||
enable_language(CXX)
|
||||
|
||||
17
build-scripts/djgpp-platform-overrides.cmake
Normal file
17
build-scripts/djgpp-platform-overrides.cmake
Normal file
@@ -0,0 +1,17 @@
|
||||
# DJGPP platform overrides for DOS
|
||||
#
|
||||
# CMake's built-in Platform/DOS.cmake assumes OpenWatcom naming conventions
|
||||
# (no prefix, .lib suffix, CMAKE_LINK_LIBRARY_SUFFIX=".lib"). DJGPP uses
|
||||
# standard Unix/GCC conventions for its system libraries (lib prefix, .a
|
||||
# suffix — e.g. libm.a).
|
||||
#
|
||||
# This file is loaded via CMAKE_USER_MAKE_RULES_OVERRIDE in the toolchain
|
||||
# file, which runs *after* the platform module has set its defaults, giving
|
||||
# us the final say on these variables.
|
||||
|
||||
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
|
||||
set(CMAKE_STATIC_LIBRARY_SUFFIX ".a")
|
||||
set(CMAKE_LINK_LIBRARY_SUFFIX "")
|
||||
set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "")
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib")
|
||||
set(CMAKE_EXECUTABLE_SUFFIX ".exe")
|
||||
82
build-scripts/i586-pc-msdosdjgpp.cmake
Normal file
82
build-scripts/i586-pc-msdosdjgpp.cmake
Normal file
@@ -0,0 +1,82 @@
|
||||
set(CMAKE_SYSTEM_NAME DOS)
|
||||
|
||||
set(DJGPP TRUE)
|
||||
|
||||
# CMake's Platform/DOS.cmake assumes OpenWatcom naming conventions (no prefix,
|
||||
# .lib suffix). DJGPP uses standard Unix/GCC conventions for its system
|
||||
# libraries (lib prefix, .a suffix — e.g. libm.a), so we override the platform
|
||||
# defaults via CMAKE_USER_MAKE_RULES_OVERRIDE, which runs *after* the platform
|
||||
# module has set its defaults, giving us the final say on these variables.
|
||||
# The path must be cached because CMake re-parses the toolchain file during
|
||||
# try_compile, where CMAKE_CURRENT_LIST_DIR may point elsewhere.
|
||||
set(DJGPP_PLATFORM_OVERRIDES "${CMAKE_CURRENT_LIST_DIR}/djgpp-platform-overrides.cmake" CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${DJGPP_PLATFORM_OVERRIDES}")
|
||||
|
||||
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
|
||||
set(CMAKE_STATIC_LIBRARY_SUFFIX ".a")
|
||||
set(CMAKE_SHARED_LIBRARY_PREFIX "")
|
||||
set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll")
|
||||
set(CMAKE_IMPORT_LIBRARY_PREFIX "lib")
|
||||
set(CMAKE_IMPORT_LIBRARY_SUFFIX ".a")
|
||||
set(CMAKE_EXECUTABLE_SUFFIX ".exe")
|
||||
set(CMAKE_LINK_LIBRARY_SUFFIX "")
|
||||
set(CMAKE_DL_LIBS "")
|
||||
|
||||
set(CMAKE_FIND_LIBRARY_PREFIXES "lib")
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
||||
|
||||
#
|
||||
# CMake toolchain file for DJGPP. Usage:
|
||||
#
|
||||
# 1. Download and extract DGJPP
|
||||
# 2. Add directory containing i586-pc-msdosdjgpp-gcc to PATH environment variable
|
||||
# 3. When configuring your CMake project, specify the toolchain file like this:
|
||||
#
|
||||
# cmake -DCMAKE_TOOLCHAIN_FILE=path/to/i586-pc-msdosdjgpp.cmake ...
|
||||
#
|
||||
|
||||
# specify the cross compiler
|
||||
find_program(CMAKE_C_COMPILER NAMES "i586-pc-msdosdjgpp-gcc" "i386-pc-msdosdjgpp-gcc" REQUIRED)
|
||||
find_program(CMAKE_CXX_COMPILER NAMES "i586-pc-msdosdjgpp-g++" "i386-pc-msdosdjgpp-g++" REQUIRED)
|
||||
|
||||
execute_process(COMMAND "${CMAKE_C_COMPILER}" -print-search-dirs
|
||||
RESULT_VARIABLE CC_SEARCH_DIRS_RESULT
|
||||
OUTPUT_VARIABLE CC_SEARCH_DIRS_OUTPUT)
|
||||
|
||||
if(CC_SEARCH_DIRS_RESULT)
|
||||
message(FATAL_ERROR "Could not determine search dirs")
|
||||
endif()
|
||||
|
||||
string(REGEX MATCH ".*libraries: (.*).*" CC_SD_LIBS "${CC_SEARCH_DIRS_OUTPUT}")
|
||||
string(STRIP "${CMAKE_MATCH_1}" CC_SEARCH_DIRS)
|
||||
string(REPLACE ":" ";" CC_SEARCH_DIRS "${CC_SEARCH_DIRS}")
|
||||
|
||||
foreach(CC_SEARCH_DIR ${CC_SEARCH_DIRS})
|
||||
if(CC_SEARCH_DIR MATCHES "=.*")
|
||||
string(REGEX MATCH "=(.*)" CC_LIB "${CC_SEARCH_DIR}")
|
||||
set(CC_SEARCH_DIR "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
if(IS_DIRECTORY "${CC_SEARCH_DIR}")
|
||||
if(IS_DIRECTORY "${CC_SEARCH_DIR}/../include" OR IS_DIRECTORY "${CC_SEARCH_DIR}/../lib" OR IS_DIRECTORY "${CC_SEARCH_DIR}/../bin")
|
||||
list(APPEND CC_ROOTS "${CC_SEARCH_DIR}/..")
|
||||
else()
|
||||
list(APPEND CC_ROOTS "${CC_SEARCH_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
list(APPEND CMAKE_FIND_ROOT_PATH ${CC_ROOTS})
|
||||
|
||||
# search for programs in the host directories
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
|
||||
# for libraries, headers and packages in the target directories
|
||||
if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_LIBRARY})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
endif()
|
||||
if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_INCLUDE})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_PACKAGE})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
endif()
|
||||
208
cmake/PreseedDOSCache.cmake
Normal file
208
cmake/PreseedDOSCache.cmake
Normal file
@@ -0,0 +1,208 @@
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "DOS")
|
||||
function(SDL_Preseed_CMakeCache)
|
||||
# SIMD intrinsics: disabled for DOS regardless of compiler version.
|
||||
# The DJGPP cross-compiler can *compile* SSE/AVX/MMX, but no real DOS
|
||||
# target machine supports them. Enabling these caused audio breakage.
|
||||
set(COMPILER_SUPPORTS_ARMNEON "" CACHE INTERNAL "Test COMPILER_SUPPORTS_ARMNEON")
|
||||
set(COMPILER_SUPPORTS_AVX "" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX")
|
||||
set(COMPILER_SUPPORTS_AVX2 "" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX2")
|
||||
set(COMPILER_SUPPORTS_AVX512F "" CACHE INTERNAL "Test COMPILER_SUPPORTS_AVX512F")
|
||||
set(COMPILER_SUPPORTS_MMX "" CACHE INTERNAL "Test COMPILER_SUPPORTS_MMX")
|
||||
set(COMPILER_SUPPORTS_SSE "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE")
|
||||
set(COMPILER_SUPPORTS_SSE2 "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE2")
|
||||
set(COMPILER_SUPPORTS_SSE3 "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE3")
|
||||
set(COMPILER_SUPPORTS_SSE4_1 "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_1")
|
||||
set(COMPILER_SUPPORTS_SSE4_2 "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SSE4_2")
|
||||
|
||||
check_c_source_compiles("
|
||||
#if !defined(__GNUC__) || (__GNUC__ < 7)
|
||||
#error Preseeding is only supported for DJGPP GCC 7 or newer
|
||||
#endif
|
||||
int main(int argc, char **argv) { return 0; }
|
||||
" CAN_PRESEED
|
||||
)
|
||||
if(CAN_PRESEED)
|
||||
set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS")
|
||||
set(COMPILER_SUPPORTS_GCC_ATOMICS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS")
|
||||
set(COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET")
|
||||
set(HAVE_CLANG_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_CLANG_COMMENT_BLOCK_COMMANDS")
|
||||
set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h")
|
||||
set(HAVE_LIBM "1" CACHE INTERNAL "Have library m")
|
||||
set(HAVE_POSIX_SPAWN "" CACHE INTERNAL "Have symbol posix_spawn")
|
||||
set(HAVE_FSEEKO "1" CACHE INTERNAL "Have symbol fseeko")
|
||||
set(HAVE_OFF64_T "1" CACHE INTERNAL "Have symbol off64_t")
|
||||
set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs")
|
||||
set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos")
|
||||
set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf")
|
||||
set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin")
|
||||
set(LIBC_HAS_ASINF "1" CACHE INTERNAL "Have symbol asinf")
|
||||
set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan")
|
||||
set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2")
|
||||
set(LIBC_HAS_ATAN2F "1" CACHE INTERNAL "Have symbol atan2f")
|
||||
set(LIBC_HAS_ATANF "1" CACHE INTERNAL "Have symbol atanf")
|
||||
set(LIBC_HAS_ATOF "1" CACHE INTERNAL "Have symbol atof")
|
||||
set(LIBC_HAS_ATOI "1" CACHE INTERNAL "Have symbol atoi")
|
||||
set(LIBC_HAS_BCOPY "1" CACHE INTERNAL "Have symbol bcopy")
|
||||
set(LIBC_HAS_CALLOC "" CACHE INTERNAL "Have symbol calloc")
|
||||
set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil")
|
||||
set(LIBC_HAS_CEILF "1" CACHE INTERNAL "Have symbol ceilf")
|
||||
set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign")
|
||||
set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf")
|
||||
set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos")
|
||||
set(LIBC_HAS_COSF "1" CACHE INTERNAL "Have symbol cosf")
|
||||
set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp")
|
||||
set(LIBC_HAS_EXPF "1" CACHE INTERNAL "Have symbol expf")
|
||||
set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs")
|
||||
set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf")
|
||||
set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h")
|
||||
set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor")
|
||||
set(LIBC_HAS_FLOORF "1" CACHE INTERNAL "Have symbol floorf")
|
||||
set(LIBC_HAS_FMOD "1" CACHE INTERNAL "Have symbol fmod")
|
||||
set(LIBC_HAS_FMODF "1" CACHE INTERNAL "Have symbol fmodf")
|
||||
set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64")
|
||||
set(LIBC_HAS_FREE "" CACHE INTERNAL "Have symbol free")
|
||||
set(LIBC_HAS_FSEEKO "1" CACHE INTERNAL "Have symbol fseeko")
|
||||
set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64 (broken in DJGPP)")
|
||||
set(LIBC_HAS_GETENV "1" CACHE INTERNAL "Have symbol getenv")
|
||||
set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h")
|
||||
set(LIBC_HAS_INDEX "1" CACHE INTERNAL "Have symbol index")
|
||||
set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h")
|
||||
set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)")
|
||||
set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)")
|
||||
set(LIBC_HAS_ISINFF "1" CACHE INTERNAL "Have include isinff(float)")
|
||||
set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)")
|
||||
set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)")
|
||||
set(LIBC_HAS_ISNANF "1" CACHE INTERNAL "Have include isnanf(float)")
|
||||
set(LIBC_HAS_ITOA "1" CACHE INTERNAL "Have symbol itoa")
|
||||
set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h")
|
||||
set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log")
|
||||
set(LIBC_HAS_LOG10 "1" CACHE INTERNAL "Have symbol log10")
|
||||
set(LIBC_HAS_LOG10F "1" CACHE INTERNAL "Have symbol log10f")
|
||||
set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf")
|
||||
set(LIBC_HAS_LROUND "1" CACHE INTERNAL "Have symbol lround")
|
||||
set(LIBC_HAS_LROUNDF "1" CACHE INTERNAL "Have symbol lroundf")
|
||||
set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc")
|
||||
set(LIBC_HAS_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h")
|
||||
set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h")
|
||||
set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp")
|
||||
set(LIBC_HAS_MEMCPY "1" CACHE INTERNAL "Have symbol memcpy")
|
||||
set(LIBC_HAS_MEMMOVE "1" CACHE INTERNAL "Have symbol memmove")
|
||||
set(LIBC_HAS_MEMORY_H "1" CACHE INTERNAL "Have include memory.h")
|
||||
set(LIBC_HAS_MEMSET "1" CACHE INTERNAL "Have symbol memset")
|
||||
set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf")
|
||||
set(LIBC_HAS_MODFF "1" CACHE INTERNAL "Have symbol modff")
|
||||
set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow")
|
||||
set(LIBC_HAS_POWF "1" CACHE INTERNAL "Have symbol powf")
|
||||
set(LIBC_HAS_PUTENV "1" CACHE INTERNAL "Have symbol putenv")
|
||||
set(LIBC_HAS_REALLOC "" CACHE INTERNAL "Have symbol realloc")
|
||||
set(LIBC_HAS_RINDEX "1" CACHE INTERNAL "Have symbol rindex")
|
||||
set(LIBC_HAS_ROUND "1" CACHE INTERNAL "Have symbol round")
|
||||
set(LIBC_HAS_ROUNDF "1" CACHE INTERNAL "Have symbol roundf")
|
||||
set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn")
|
||||
set(LIBC_HAS_SCALBNF "1" CACHE INTERNAL "Have symbol scalbnf")
|
||||
set(LIBC_HAS_SETENV "1" CACHE INTERNAL "Have symbol setenv")
|
||||
set(LIBC_HAS_SIGNAL_H "1" CACHE INTERNAL "Have include signal.h")
|
||||
set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin")
|
||||
set(LIBC_HAS_SINF "1" CACHE INTERNAL "Have symbol sinf")
|
||||
set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr")
|
||||
set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt")
|
||||
set(LIBC_HAS_SQRTF "1" CACHE INTERNAL "Have symbol sqrtf")
|
||||
set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf")
|
||||
set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h")
|
||||
set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h")
|
||||
set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h")
|
||||
set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h")
|
||||
set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h")
|
||||
set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h")
|
||||
set(LIBC_HAS_STRCASESTR "" CACHE INTERNAL "Have symbol strcasestr")
|
||||
set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr")
|
||||
set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp")
|
||||
set(LIBC_HAS_STRINGS_H "1" CACHE INTERNAL "Have include strings.h")
|
||||
set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h")
|
||||
set(LIBC_HAS_STRLCAT "1" CACHE INTERNAL "Have symbol strlcat")
|
||||
set(LIBC_HAS_STRLCPY "1" CACHE INTERNAL "Have symbol strlcpy")
|
||||
set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen")
|
||||
set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp")
|
||||
set(LIBC_HAS_STRNLEN "1" CACHE INTERNAL "Have symbol strnlen")
|
||||
set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr")
|
||||
set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk")
|
||||
set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr")
|
||||
set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr")
|
||||
set(LIBC_HAS_STRTOD "1" CACHE INTERNAL "Have symbol strtod")
|
||||
set(LIBC_HAS_STRTOK_R "1" CACHE INTERNAL "Have symbol strtok_r")
|
||||
set(LIBC_HAS_STRTOL "1" CACHE INTERNAL "Have symbol strtol")
|
||||
set(LIBC_HAS_STRTOLL "1" CACHE INTERNAL "Have symbol strtoll")
|
||||
set(LIBC_HAS_STRTOUL "1" CACHE INTERNAL "Have symbol strtoul")
|
||||
set(LIBC_HAS_STRTOULL "1" CACHE INTERNAL "Have symbol strtoull")
|
||||
set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h")
|
||||
set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan")
|
||||
set(LIBC_HAS_TANF "1" CACHE INTERNAL "Have symbol tanf")
|
||||
set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h")
|
||||
set(LIBC_HAS_TRUNC "1" CACHE INTERNAL "Have symbol trunc")
|
||||
set(LIBC_HAS_TRUNCF "1" CACHE INTERNAL "Have symbol truncf")
|
||||
set(LIBC_HAS_UNSETENV "1" CACHE INTERNAL "Have symbol unsetenv")
|
||||
set(LIBC_HAS_VSNPRINTF "1" CACHE INTERNAL "Have symbol vsnprintf")
|
||||
set(LIBC_HAS_VSSCANF "1" CACHE INTERNAL "Have symbol vsscanf")
|
||||
set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h")
|
||||
set(LIBC_HAS_WCSCMP "" CACHE INTERNAL "Have symbol wcscmp")
|
||||
set(LIBC_HAS_WCSDUP "" CACHE INTERNAL "Have symbol wcsdup")
|
||||
set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat")
|
||||
set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy")
|
||||
set(LIBC_HAS_WCSLEN "" CACHE INTERNAL "Have symbol wcslen")
|
||||
set(LIBC_HAS_WCSNCMP "" CACHE INTERNAL "Have symbol wcsncmp")
|
||||
set(LIBC_HAS_WCSNLEN "" CACHE INTERNAL "Have symbol wcsnlen")
|
||||
set(LIBC_HAS_WCSSTR "" CACHE INTERNAL "Have symbol wcsstr")
|
||||
set(LIBC_HAS_WCSTOL "" CACHE INTERNAL "Have symbol wcstol")
|
||||
set(LIBC_HAS__EXIT "1" CACHE INTERNAL "Have symbol _Exit")
|
||||
set(LIBC_HAS__I64TOA "" CACHE INTERNAL "Have symbol _i64toa")
|
||||
set(LIBC_HAS__LTOA "" CACHE INTERNAL "Have symbol _ltoa")
|
||||
set(LIBC_HAS__STRREV "" CACHE INTERNAL "Have symbol _strrev")
|
||||
set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa")
|
||||
set(LIBC_HAS__ULTOA "" CACHE INTERNAL "Have symbol _ultoa")
|
||||
set(LIBC_HAS__WCSDUP "" CACHE INTERNAL "Have symbol _wcsdup")
|
||||
set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__")
|
||||
set(HAVE_GCC_WALL "1" CACHE INTERNAL "Test HAVE_GCC_WALL")
|
||||
set(HAVE_GCC_WUNDEF "1" CACHE INTERNAL "Test HAVE_GCC_WUNDEF")
|
||||
set(HAVE_GCC_WFLOAT_CONVERSION "1" CACHE INTERNAL "Test HAVE_GCC_WFLOAT_CONVERSION")
|
||||
set(HAVE_GCC_NO_STRICT_ALIASING "1" CACHE INTERNAL "Test HAVE_GCC_NO_STRICT_ALIASING")
|
||||
set(HAVE_GCC_WDOCUMENTATION "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION")
|
||||
set(HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND")
|
||||
set(HAVE_GCC_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_GCC_COMMENT_BLOCK_COMMANDS")
|
||||
set(HAVE_GCC_WSHADOW "1" CACHE INTERNAL "Test HAVE_GCC_WSHADOW")
|
||||
set(HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS "1" CACHE INTERNAL "Test HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS")
|
||||
set(HAVE_GCC_WIMPLICIT_FALLTHROUGH "1" CACHE INTERNAL "Test HAVE_GCC_WIMPLICIT_FALLTHROUGH")
|
||||
set(HAVE_GCC_FVISIBILITY "" CACHE INTERNAL "Test HAVE_GCC_FVISIBILITY")
|
||||
set(HAVE_ST_MTIM "" CACHE INTERNAL "Test HAVE_ST_MTIM")
|
||||
set(HAVE_LD_VERSION_SCRIPT "1" CACHE INTERNAL "Test HAVE_LD_VERSION_SCRIPT")
|
||||
set(HAVE_WL_VERSION_SCRIPT "1" CACHE INTERNAL "Test HAVE_WL_VERSION_SCRIPT")
|
||||
set(LINKER_SUPPORTS_VERSION_SCRIPT "1" CACHE INTERNAL "Test LINKER_SUPPORTS_VERSION_SCRIPT")
|
||||
set(LINKER_SUPPORTS_WL_NO_UNDEFINED "1" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED")
|
||||
set(ICONV_IN_LIBC "" CACHE INTERNAL "Test ICONV_IN_LIBC")
|
||||
set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV")
|
||||
set(HAVE_GETPAGESIZE "1" CACHE INTERNAL "Have symbol getpagesize")
|
||||
set(HAVE_SIGACTION "1" CACHE INTERNAL "Have symbol sigaction")
|
||||
set(HAVE_SA_SIGACTION "" CACHE INTERNAL "Have symbol sa_sigaction")
|
||||
set(HAVE_SETJMP "1" CACHE INTERNAL "Have symbol setjmp")
|
||||
set(HAVE_NANOSLEEP "" CACHE INTERNAL "Have symbol nanosleep")
|
||||
set(HAVE_GMTIME_R "1" CACHE INTERNAL "Have symbol gmtime_r")
|
||||
set(HAVE_LOCALTIME_R "1" CACHE INTERNAL "Have symbol localtime_r")
|
||||
set(HAVE_NL_LANGINFO "" CACHE INTERNAL "Have symbol nl_langinfo")
|
||||
set(HAVE_SYSCONF "1" CACHE INTERNAL "Have symbol sysconf")
|
||||
set(HAVE_SYSCTLBYNAME "" CACHE INTERNAL "Have symbol sysctlbyname")
|
||||
set(HAVE_GETAUXVAL "" CACHE INTERNAL "Have symbol getauxval")
|
||||
set(HAVE_ELF_AUX_INFO "" CACHE INTERNAL "Have symbol elf_aux_info")
|
||||
set(HAVE_POLL "" CACHE INTERNAL "Have symbol poll")
|
||||
set(HAVE_MEMFD_CREATE "" CACHE INTERNAL "Have symbol memfd_create")
|
||||
set(HAVE_POSIX_FALLOCATE "" CACHE INTERNAL "Have symbol posix_fallocate")
|
||||
set(HAVE_DLOPEN_IN_LIBC "" CACHE INTERNAL "Have symbol dlopen")
|
||||
set(HAVE_GETHOSTNAME "1" CACHE INTERNAL "Have symbol gethostname")
|
||||
set(HAVE_SIGTIMEDWAIT "" CACHE INTERNAL "Have symbol sigtimedwait")
|
||||
set(HAVE_PPOLL "" CACHE INTERNAL "Have symbol ppoll")
|
||||
set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol addchdir")
|
||||
set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "" CACHE INTERNAL "Have symbol addchdir_np")
|
||||
set(HAVE_FDATASYNC "" CACHE INTERNAL "Have symbol fdatasync")
|
||||
set(HAVE_GETRESUID "" CACHE INTERNAL "Have symbol getresuid")
|
||||
set(HAVE_GETRESGID "" CACHE INTERNAL "Have symbol getresgid")
|
||||
endif()
|
||||
endfunction()
|
||||
endif()
|
||||
@@ -425,7 +425,7 @@ function(SDL_PrintSummary)
|
||||
message(STATUS "")
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT (ANDROID OR APPLE OR EMSCRIPTEN OR HAIKU OR RISCOS))
|
||||
if(UNIX AND NOT (ANDROID OR APPLE OR EMSCRIPTEN OR HAIKU OR RISCOS OR DJGPP))
|
||||
if(NOT (HAVE_X11 OR HAVE_WAYLAND))
|
||||
if(NOT SDL_UNIX_CONSOLE_BUILD)
|
||||
message(FATAL_ERROR
|
||||
|
||||
@@ -22,6 +22,8 @@ function(SDL_DetectCMakePlatform)
|
||||
set(sdl_cmake_platform Haiku)
|
||||
elseif(NINTENDO_3DS)
|
||||
set(sdl_cmake_platform n3ds)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "DOS")
|
||||
set(sdl_cmake_platform dos)
|
||||
elseif(NGAGESDK)
|
||||
set(sdl_cmake_platform ngage)
|
||||
elseif(PS2)
|
||||
|
||||
90
docs/README-dos.md
Normal file
90
docs/README-dos.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# DOS
|
||||
|
||||
SDL port for MS-DOS using the [DJGPP](https://www.delorie.com/djgpp/) GCC cross-compiler.
|
||||
|
||||
## Building
|
||||
|
||||
To build for DOS, make sure you have a DJGPP cross-compiler in your PATH and run:
|
||||
|
||||
```bash
|
||||
cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=build-scripts/i586-pc-msdosdjgpp.cmake -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
The toolchain file looks for `i586-pc-msdosdjgpp-gcc` or `i386-pc-msdosdjgpp-gcc` in PATH.
|
||||
|
||||
## Running
|
||||
|
||||
DOS executables require a DPMI host. Place `CWSDPMI.EXE` next to your executable.
|
||||
|
||||
To run in DOSBox:
|
||||
|
||||
```bash
|
||||
dosbox myapp.exe
|
||||
```
|
||||
|
||||
## System Requirements
|
||||
|
||||
| Component | Minimum |
|
||||
| --------- | ------------------------------ |
|
||||
| CPU | i386 or higher |
|
||||
| RAM | 4 MB |
|
||||
| Video | VGA (256-color mode 13h) |
|
||||
| Audio | Sound Blaster |
|
||||
| DPMI | CWSDPMI.exe or compatible host |
|
||||
|
||||
Higher resolutions (640×480 and above) require a VESA VBE 1.2+ compatible video card.
|
||||
|
||||
## Notes
|
||||
|
||||
### Memory Model
|
||||
|
||||
The DOS port produces 32-bit protected-mode DPMI executables. It uses the "fat DS" nearptr trick (`__djgpp_nearptr_enable()`) to convert physical addresses to usable C pointers. This allows direct access to the VESA linear framebuffer and DMA buffers from C code without segment descriptor manipulation.
|
||||
|
||||
SDL on DOS requires the fat DS trick. `SDL_RunApp()` enables it automatically via `SDL_main.h`. If you define `SDL_MAIN_HANDLED`, you must call `__djgpp_nearptr_enable()` yourself before initializing SDL, or video initialization will fail with a clear error message.
|
||||
|
||||
### Threading
|
||||
|
||||
DOS has no OS-level threads. SDL on DOS implements cooperative threading via a mini-scheduler that uses `setjmp`/`longjmp` for context switching. Threads are never preempted mid-instruction. Context switches occur only at explicit yield points such as `SDL_Delay` and the event pump, so make sure your main loop calls `SDL_PumpEvents` or `SDL_Delay` regularly. `SDL_Delay(0)` yields to other threads without sleeping and is safe to call in tight loops. In some cases, like a load screen, a longer delay (e.g. `SDL_Delay(16)`) may be needed to give background threads enough CPU time.
|
||||
|
||||
### Video
|
||||
|
||||
The video driver supports VGA mode 13h (320×200×256) on any VGA card, and higher resolutions via VESA BIOS Extensions (VBE 1.2+). Both linear framebuffer (VBE 2.0+) and banked framebuffer modes are supported. Hardware page-flipping is used for tear-free rendering when available.
|
||||
|
||||
Only software rendering is supported. There is no GPU renderer.
|
||||
|
||||
All video modes are effectively fullscreen. When creating a window, the driver selects the closest available video mode to the requested size.
|
||||
|
||||
8-bit indexed color (INDEX8) modes with programmable VGA DAC palettes are supported but will only be used when explicitly requested via the `SDL_PIXELFORMAT` window creation property.
|
||||
|
||||
EGA and CGA cards are not supported. The driver detects VGA hardware at initialization and will fail with a clear error message if VGA is not present.
|
||||
|
||||
#### Direct Framebuffer Hint
|
||||
|
||||
Setting `SDL_HINT_DOS_ALLOW_DIRECT_FRAMEBUFFER` to `"1"` before calling `SDL_GetWindowSurface()` enables a fast path that skips the normal surface copy. `SDL_UpdateWindowSurface()` copies the system-RAM surface directly to VRAM via `dosmemput` and only programs the VGA DAC palette when it changes. Page flipping is done without waiting for vblank. No software cursor compositing is performed.
|
||||
|
||||
This mode is designed for applications like Quake that manage their own rendering and want maximum frame throughput. The trade-offs are:
|
||||
|
||||
- No vsync. Tearing is expected.
|
||||
- No software cursor. The application must draw its own cursor if needed.
|
||||
- Reading back the surface may be slow on real hardware (uncached VRAM).
|
||||
|
||||
The hint must be set before the first call to `SDL_GetWindowSurface()`. Changing it after that has no effect.
|
||||
|
||||
### Audio
|
||||
|
||||
Sound Blaster support is available for SB16 (16-bit stereo), SB Pro (8-bit stereo), and SB 2.0/1.x (8-bit mono). Configured automatically from the `BLASTER` environment variable.
|
||||
|
||||
A ring buffer sits between the SDL audio pipeline and the DMA hardware. The audio thread fills the ring buffer cooperatively, and the Sound Blaster IRQ handler copies data from the ring buffer to the DMA buffer directly. This gives roughly 45 ms of cushion before audio would stutter. If your game runs at 22 fps or above, audio will be glitch-free with no extra effort. Below 20 fps, adding a `SDL_Delay(0)` in the middle of your game loop should be enough to keep the ring buffer fed.
|
||||
|
||||
Audio recording is not implemented.
|
||||
|
||||
### Input
|
||||
|
||||
- Keyboard: IRQ1-driven with full extended scancode (0xE0 prefix) support.
|
||||
- Mouse: INT 33h mouse driver with relative motion via mickeys.
|
||||
- Joystick: gameport joystick via BIOS INT 15h (axes) and direct port 0x201 reads (buttons) with software calibration.
|
||||
|
||||
### Limitations
|
||||
|
||||
- No shared library / dynamic loading support (no `SDL_LoadObject`). DXE support may be added in the future.
|
||||
@@ -724,6 +724,23 @@ extern "C" {
|
||||
*/
|
||||
#define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS"
|
||||
|
||||
/**
|
||||
* A variable that enables a fast framebuffer path on DOS.
|
||||
*
|
||||
* When set to "1", SDL_UpdateWindowSurface() copies the system-RAM surface
|
||||
* directly to VRAM and skips software cursor compositing and vsync.
|
||||
*
|
||||
* The variable can be set to the following values:
|
||||
*
|
||||
* - "0": Use the normal path with cursor compositing and vsync. (default)
|
||||
* - "1": Use the fast direct-to-VRAM path when available.
|
||||
*
|
||||
* This hint must be set before the first call to SDL_GetWindowSurface().
|
||||
*
|
||||
* \since This hint is available since SDL 3.6.0.
|
||||
*/
|
||||
#define SDL_HINT_DOS_ALLOW_DIRECT_FRAMEBUFFER "SDL_DOS_ALLOW_DIRECT_FRAMEBUFFER"
|
||||
|
||||
/**
|
||||
* Set the level of checking for invalid parameters passed to SDL functions.
|
||||
*
|
||||
|
||||
@@ -219,6 +219,19 @@
|
||||
void reset_IOP(); \
|
||||
void reset_IOP() {}
|
||||
|
||||
#elif defined(SDL_PLATFORM_DOS)
|
||||
/*
|
||||
On DOS, SDL provides a main function that sets up memory
|
||||
page locking (code, data, stack are locked, future
|
||||
malloc calls are not locked), and sets up the "fat DS"
|
||||
trick, so we can use C pointers from protected mode that
|
||||
access conventional memory. SDL _requires_ the "fat DS"
|
||||
trick!
|
||||
|
||||
If you provide this yourself, you may define SDL_MAIN_HANDLED
|
||||
*/
|
||||
#define SDL_MAIN_AVAILABLE
|
||||
|
||||
#elif defined(SDL_PLATFORM_3DS)
|
||||
/*
|
||||
On N3DS, SDL provides a main function that sets up the screens
|
||||
|
||||
@@ -484,6 +484,16 @@
|
||||
#define SDL_PLATFORM_NGAGE 1
|
||||
#endif
|
||||
|
||||
#ifdef __MSDOS__
|
||||
|
||||
/**
|
||||
* A preprocessor macro that is only defined if compiling for MS-DOS.
|
||||
*
|
||||
* \since This macro is available since SDL 3.6.0.
|
||||
*/
|
||||
#define SDL_PLATFORM_DOS 1
|
||||
#endif
|
||||
|
||||
#ifdef __GNU__
|
||||
|
||||
/**
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
#cmakedefine SDL_AUDIO_DRIVER_N3DS 1
|
||||
#cmakedefine SDL_AUDIO_DRIVER_NGAGE 1
|
||||
#cmakedefine SDL_AUDIO_DRIVER_QNX 1
|
||||
|
||||
#cmakedefine SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER 1
|
||||
#cmakedefine SDL_AUDIO_DRIVER_PRIVATE 1
|
||||
|
||||
/* Enable various input drivers */
|
||||
@@ -303,6 +303,7 @@
|
||||
#cmakedefine SDL_HAVE_MACHINE_JOYSTICK_H 1
|
||||
#cmakedefine SDL_JOYSTICK_ANDROID 1
|
||||
#cmakedefine SDL_JOYSTICK_DINPUT 1
|
||||
#cmakedefine SDL_JOYSTICK_DOS 1
|
||||
#cmakedefine SDL_JOYSTICK_DUMMY 1
|
||||
#cmakedefine SDL_JOYSTICK_EMSCRIPTEN 1
|
||||
#cmakedefine SDL_JOYSTICK_GAMEINPUT 1
|
||||
@@ -370,6 +371,7 @@
|
||||
#cmakedefine SDL_THREAD_PSP 1
|
||||
#cmakedefine SDL_THREAD_PS2 1
|
||||
#cmakedefine SDL_THREAD_N3DS 1
|
||||
#cmakedefine SDL_THREAD_DOS 1
|
||||
|
||||
#cmakedefine SDL_THREAD_PRIVATE 1
|
||||
|
||||
@@ -392,6 +394,7 @@
|
||||
#cmakedefine SDL_TIMER_PSP 1
|
||||
#cmakedefine SDL_TIMER_PS2 1
|
||||
#cmakedefine SDL_TIMER_N3DS 1
|
||||
#cmakedefine SDL_TIMER_DOS 1
|
||||
|
||||
#cmakedefine SDL_TIMER_PRIVATE 1
|
||||
|
||||
@@ -448,6 +451,7 @@
|
||||
#cmakedefine SDL_VIDEO_DRIVER_X11_XSYNC 1
|
||||
#cmakedefine SDL_VIDEO_DRIVER_X11_XTEST 1
|
||||
#cmakedefine SDL_VIDEO_DRIVER_QNX 1
|
||||
#cmakedefine SDL_VIDEO_DRIVER_DOSVESA 1
|
||||
|
||||
#cmakedefine SDL_VIDEO_DRIVER_PRIVATE 1
|
||||
|
||||
@@ -520,6 +524,7 @@
|
||||
#cmakedefine SDL_FILESYSTEM_PSP 1
|
||||
#cmakedefine SDL_FILESYSTEM_PS2 1
|
||||
#cmakedefine SDL_FILESYSTEM_N3DS 1
|
||||
#cmakedefine SDL_FILESYSTEM_DOS 1
|
||||
|
||||
#cmakedefine SDL_FILESYSTEM_PRIVATE 1
|
||||
|
||||
|
||||
@@ -771,6 +771,8 @@ const char *SDL_GetPlatform(void)
|
||||
return "Linux";
|
||||
#elif defined(__MINT__)
|
||||
return "Atari MiNT";
|
||||
#elif defined(SDL_PLATFORM_MSDOS)
|
||||
return "MS-DOS";
|
||||
#elif defined(SDL_PLATFORM_MACOS)
|
||||
return "macOS";
|
||||
#elif defined(SDL_PLATFORM_NETBSD)
|
||||
|
||||
@@ -92,6 +92,9 @@ static const AudioBootStrap *const bootstrap[] = {
|
||||
#ifdef SDL_AUDIO_DRIVER_QNX
|
||||
&QSAAUDIO_bootstrap,
|
||||
#endif
|
||||
#ifdef SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER
|
||||
&DOSSOUNDBLASTER_bootstrap,
|
||||
#endif
|
||||
#ifdef SDL_AUDIO_DRIVER_DISK
|
||||
&DISKAUDIO_bootstrap,
|
||||
#endif
|
||||
|
||||
@@ -390,5 +390,6 @@ extern AudioBootStrap N3DSAUDIO_bootstrap;
|
||||
extern AudioBootStrap NGAGEAUDIO_bootstrap;
|
||||
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
|
||||
extern AudioBootStrap QSAAUDIO_bootstrap;
|
||||
extern AudioBootStrap DOSSOUNDBLASTER_bootstrap;
|
||||
|
||||
#endif // SDL_sysaudio_h_
|
||||
|
||||
631
src/audio/dos/SDL_dosaudio_sb.c
Normal file
631
src/audio/dos/SDL_dosaudio_sb.c
Normal file
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
#include "SDL_dosaudio_sb.h"
|
||||
|
||||
// Set to 1 to force 8-bit mono (pre-SB16) code path even on SB16 hardware.
|
||||
// Useful for testing in DOSBox which always emulates an SB16 (DSP 4.x).
|
||||
#define FORCE_SB_8BIT 0
|
||||
|
||||
static int soundblaster_base_port = -1;
|
||||
static int soundblaster_irq = -1;
|
||||
static int soundblaster_dma_channel = -1;
|
||||
static int soundblaster_highdma_channel = -1;
|
||||
static int soundblaster_version = -1;
|
||||
static int soundblaster_version_minor = -1;
|
||||
static bool soundblaster_is_sb16 = false; // false when FORCE_SB_8BIT or DSP < 4
|
||||
static Uint8 soundblaster_silence_value = 0;
|
||||
|
||||
static void ResetSoundBlasterDSP(void)
|
||||
{
|
||||
// reset the DSP.
|
||||
const int reset_port = soundblaster_base_port + 0x6;
|
||||
outportb(reset_port, 1);
|
||||
SDL_DelayPrecise(3000); // wait at least 3 microseconds for hardware to see it.
|
||||
outportb(reset_port, 0);
|
||||
}
|
||||
|
||||
static bool ReadSoundBlasterReady(void)
|
||||
{
|
||||
const int ready_port = soundblaster_base_port + 0xE;
|
||||
return ((inportb(ready_port) & (1 << 7)) != 0);
|
||||
}
|
||||
|
||||
static void WriteSoundBlasterDSP(const Uint8 val)
|
||||
{
|
||||
const int port = soundblaster_base_port + 0xC;
|
||||
int timeout = 100000;
|
||||
while ((inportb(port) & (1 << 7)) && --timeout > 0) { /* spin until ready or timeout */
|
||||
}
|
||||
outportb(port, val);
|
||||
}
|
||||
|
||||
static Uint8 ReadSoundBlasterDSP(void)
|
||||
{
|
||||
const int query_port = soundblaster_base_port + 0xA;
|
||||
int timeout = 100000;
|
||||
while (!ReadSoundBlasterReady() && --timeout > 0) { /* spin until ready or timeout */
|
||||
}
|
||||
return (Uint8)inportb(query_port);
|
||||
}
|
||||
|
||||
// The ISR copies audio from a pre-allocated ring buffer directly into the
|
||||
// DMA half-buffer. The SDL audio thread fills the ring buffer cooperatively
|
||||
// (using the full SDL pipeline with all its allocations and mutexes), and
|
||||
// the ISR just does a memcpy (no SDL calls, no DPMI, no allocator).
|
||||
|
||||
// Number of DMA half-buffers that fit in the ring. Must be a power of two.
|
||||
// 4 chunks is ~45 ms at 44100 Hz, enough headroom for 22 fps frame times.
|
||||
#define RING_BUFFER_CHUNKS 4
|
||||
|
||||
// All of the following statics are memory-locked, making them safe to access
|
||||
// from the ISR without risking a page fault or DPMI re-entrance.
|
||||
|
||||
// ISR-cached copies of device state (avoids chasing heap pointers in IRQ context).
|
||||
static volatile int isr_irq_ack_port = 0;
|
||||
|
||||
// ISR-visible ring buffer state (all memory-locked).
|
||||
static volatile int isr_ring_read = 0;
|
||||
static volatile int isr_ring_write = 0;
|
||||
static int isr_ring_size = 0; // ring_size (power-of-2 bytes)
|
||||
static int isr_ring_mask = 0; // ring_size - 1
|
||||
static int isr_chunk_size = 0; // one DMA half-buffer, in bytes
|
||||
static Uint8 *isr_ring_buffer = NULL; // the ring itself (allocated and locked)
|
||||
static Uint8 *isr_dma_buffer = NULL; // pointer to the DMA double-buffer
|
||||
static int isr_dma_halfdma = 0; // half the DMA buffer size, in bytes
|
||||
static int isr_dma_channel = 0;
|
||||
static bool isr_is_16bit = false;
|
||||
static Uint8 isr_silence_value = 0;
|
||||
|
||||
// Copy `len` bytes from the ring buffer at position `pos` into `dst`,
|
||||
// handling the power-of-2 wrap. All pointers are memory-locked.
|
||||
static void RingCopyOut(Uint8 *dst, int pos, int len)
|
||||
{
|
||||
const int mask = isr_ring_mask;
|
||||
const int start = pos & mask;
|
||||
const int first = (start + len <= isr_ring_size) ? len : (isr_ring_size - start);
|
||||
SDL_memcpy(dst, isr_ring_buffer + start, first);
|
||||
if (first < len) {
|
||||
SDL_memcpy(dst + first, isr_ring_buffer, len - first);
|
||||
}
|
||||
}
|
||||
static void RingCopyOut_End(void) {}
|
||||
|
||||
// Determine which DMA half-buffer the hardware is NOT currently playing
|
||||
// (i.e. the one we should fill). Uses ISR-cached statics so we don't
|
||||
// chase any heap pointers.
|
||||
static Uint8 *ISR_GetDMAHalf(void)
|
||||
{
|
||||
int count;
|
||||
if (isr_is_16bit) {
|
||||
outportb(0xD8, 0x00);
|
||||
count = (int)inportb(0xC0 + (isr_dma_channel - 4) * 4 + 2);
|
||||
count += (int)inportb(0xC0 + (isr_dma_channel - 4) * 4 + 2) << 8;
|
||||
return isr_dma_buffer + (count < (isr_dma_halfdma / 2) ? 0 : isr_dma_halfdma);
|
||||
} else {
|
||||
outportb(0x0C, 0x00);
|
||||
count = (int)inportb(isr_dma_channel * 2 + 1);
|
||||
count += (int)inportb(isr_dma_channel * 2 + 1) << 8;
|
||||
return isr_dma_buffer + (count < isr_dma_halfdma ? 0 : isr_dma_halfdma);
|
||||
}
|
||||
}
|
||||
static void ISR_GetDMAHalf_End(void) {}
|
||||
|
||||
// The IRQ handler. Copies one chunk from the ring buffer into the DMA
|
||||
// half-buffer that the hardware isn't currently playing. If the ring is
|
||||
// empty it fills with silence (no stutter, just a brief gap).
|
||||
//
|
||||
// This function touches ONLY memory-locked data and does ONLY port I/O and
|
||||
// memcpy. No DPMI, no malloc, no mutex, no FPU.
|
||||
static void SoundBlasterIRQHandler(void)
|
||||
{
|
||||
// Acknowledge hardware first.
|
||||
inportb(isr_irq_ack_port);
|
||||
DOS_EndOfInterrupt(soundblaster_irq);
|
||||
|
||||
Uint8 *dma_dst = ISR_GetDMAHalf();
|
||||
|
||||
// How many bytes are available in the ring?
|
||||
const int avail = isr_ring_write - isr_ring_read; // both are monotonic
|
||||
|
||||
if (avail >= isr_chunk_size) {
|
||||
RingCopyOut(dma_dst, isr_ring_read, isr_chunk_size);
|
||||
isr_ring_read += isr_chunk_size;
|
||||
} else {
|
||||
// Ring underrun: fill with silence so we don't replay stale audio.
|
||||
SDL_memset(dma_dst, isr_silence_value, isr_chunk_size);
|
||||
}
|
||||
}
|
||||
static void SoundBlasterIRQHandler_End(void) {}
|
||||
|
||||
// Wait until the ring buffer has room for one more chunk.
|
||||
// The audio thread keeps yielding so the game's main thread can run while
|
||||
// we wait. Because the ISR is steadily draining the ring, this only blocks
|
||||
// when the ring is completely full, which is a good problem to have.
|
||||
static bool DOSSOUNDBLASTER_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const int size = hidden->ring_size;
|
||||
|
||||
for (;;) {
|
||||
// Available space = ring_size - (write - read).
|
||||
// ring_write is ours (audio thread only), ring_read is advanced by
|
||||
// the ISR. Read the ISR's copy so we see the latest drain position.
|
||||
const int used = hidden->ring_write - isr_ring_read;
|
||||
if ((size - used) >= hidden->chunk_size) {
|
||||
return true; // room for at least one chunk
|
||||
}
|
||||
DOS_Yield();
|
||||
}
|
||||
}
|
||||
|
||||
static bool DOSSOUNDBLASTER_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool is_sb16 = soundblaster_is_sb16;
|
||||
|
||||
if (is_sb16) {
|
||||
// SB16 (DSP >= 4): 16-bit stereo signed
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
device->spec.channels = 2;
|
||||
} else if (soundblaster_version >= 3) {
|
||||
// SB Pro (DSP 3.x): 8-bit stereo unsigned.
|
||||
// Max 22050 Hz in stereo (hardware interleaves L/R at double the rate).
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
device->spec.channels = 2;
|
||||
} else {
|
||||
// SB 2.0 (DSP 2.x) and SB 1.x: 8-bit mono unsigned.
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
device->spec.channels = 1;
|
||||
}
|
||||
|
||||
// Accept whatever frequency SDL3's audio layer passes in. For SB16 (DSP >= 4)
|
||||
// the hardware supports 5000–44100 Hz via DSP command 0x41. For pre-SB16,
|
||||
// clamp to hardware limits:
|
||||
// SB 1.x: max ~23 kHz mono
|
||||
// SB 2.0 (DSP 2.x): max 44100 Hz mono (high-speed), ~23 kHz normal
|
||||
// SB Pro (DSP 3.x): max 22050 Hz stereo, max 44100 Hz mono
|
||||
if (!is_sb16 && device->spec.freq > 22050) {
|
||||
device->spec.freq = 22050; // clamp to safe max for pre-SB16
|
||||
}
|
||||
device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq);
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
SDL_Log("SOUNDBLASTER: Opening at %d Hz, %d channels, format 0x%X, %d sample frames (DSP %d.%d, %s)",
|
||||
device->spec.freq, device->spec.channels, device->spec.format, device->sample_frames,
|
||||
soundblaster_version, soundblaster_version_minor, is_sb16 ? "SB16" : "pre-SB16");
|
||||
|
||||
if (device->buffer_size > (32 * 1024)) {
|
||||
return SDL_SetError("Buffer size is too large (choose smaller audio format and/or fewer sample frames)"); // DMA buffer has to fit in 64K segment, so buffer_size has to be half that, as we double it.
|
||||
}
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden = hidden;
|
||||
hidden->is_16bit = is_sb16;
|
||||
|
||||
ResetSoundBlasterDSP();
|
||||
|
||||
// allocate conventional memory for the DMA buffer.
|
||||
hidden->dma_channel = is_sb16 ? soundblaster_highdma_channel : soundblaster_dma_channel;
|
||||
if (hidden->dma_channel < 0) {
|
||||
SDL_free(hidden);
|
||||
return SDL_SetError("No %s DMA channel configured in BLASTER environment variable",
|
||||
is_sb16 ? "high (16-bit)" : "low (8-bit)");
|
||||
}
|
||||
hidden->dma_buflen = device->buffer_size * 2;
|
||||
hidden->dma_buffer = (Uint8 *)DOS_AllocateDMAMemory(hidden->dma_buflen, &hidden->dma_seginfo);
|
||||
if (!hidden->dma_buffer) {
|
||||
return SDL_SetError("Couldn't allocate Sound Blaster DMA buffer!");
|
||||
}
|
||||
|
||||
SDL_Log("SOUNDBLASTER: Allocated %d bytes of conventional memory at segment %d (ptr=%p)", (int)hidden->dma_buflen, (int)hidden->dma_seginfo.rm_segment, hidden->dma_buffer);
|
||||
|
||||
// silence the DMA buffer to start
|
||||
SDL_memset(hidden->dma_buffer, soundblaster_silence_value, hidden->dma_buflen);
|
||||
|
||||
// set up DMA controller.
|
||||
const Uint32 physical = DOS_LinearToPhysical(hidden->dma_buffer);
|
||||
const Uint8 physical_page = (physical >> 16) & 0xFF;
|
||||
|
||||
if (is_sb16) {
|
||||
// High DMA (16-bit, channels 5-7): ports in 0xC0-0xDF range, counts in words.
|
||||
const int dma_words = (hidden->dma_buflen / 2) - 1;
|
||||
outportb(0xD4, 0x04 | hidden->dma_channel); // mask the DMA channel
|
||||
outportb(0xD6, 0x58 | (hidden->dma_channel - 4)); // mode: single, read, auto-init
|
||||
static const int high_page_ports[] = { 0, 0, 0, 0, 0, 0x8B, 0x89, 0x8A }; // DMA page register ports for channels 5-7
|
||||
outportb(high_page_ports[hidden->dma_channel], physical_page); // page to transfer
|
||||
outportb(0xD8, 0x00); // clear the flip-flop
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4, (Uint8)((physical >> 1) & 0xFF)); // offset low (word address)
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4, (Uint8)((physical >> 9) & 0xFF)); // offset high
|
||||
outportb(0xD8, 0x00); // clear the flip-flop
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4 + 2, (Uint8)(dma_words & 0xFF)); // count low
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4 + 2, (Uint8)((dma_words >> 8) & 0xFF)); // count high
|
||||
outportb(0xD4, hidden->dma_channel & ~4); // unmask the DMA channel
|
||||
} else {
|
||||
// Low DMA (8-bit, channels 0-3): ports in 0x00-0x0F range, counts in bytes.
|
||||
static const int page_ports[] = { 0x87, 0x83, 0x81, 0x82 }; // DMA page register ports for channels 0-3 (yes, they're out of order — that's how the IBM PC DMA controller works)
|
||||
const int dma_bytes = hidden->dma_buflen - 1;
|
||||
outportb(0x0A, 0x04 | hidden->dma_channel); // mask the DMA channel
|
||||
outportb(0x0B, 0x58 | hidden->dma_channel); // mode: single, read, auto-init
|
||||
outportb(page_ports[hidden->dma_channel], physical_page); // page to transfer
|
||||
outportb(0x0C, 0x00); // clear the flip-flop
|
||||
outportb(hidden->dma_channel * 2, (Uint8)(physical & 0xFF)); // offset low (byte address)
|
||||
outportb(hidden->dma_channel * 2, (Uint8)((physical >> 8) & 0xFF)); // offset high
|
||||
outportb(0x0C, 0x00); // clear the flip-flop
|
||||
outportb(hidden->dma_channel * 2 + 1, (Uint8)(dma_bytes & 0xFF)); // count low
|
||||
outportb(hidden->dma_channel * 2 + 1, (Uint8)((dma_bytes >> 8) & 0xFF)); // count high
|
||||
outportb(0x0A, hidden->dma_channel); // unmask the DMA channel (just the channel number, no bit 2)
|
||||
}
|
||||
|
||||
// Cache the IRQ ack port so the ISR doesn't chase pointers.
|
||||
isr_irq_ack_port = is_sb16 ? (soundblaster_base_port + 0x0F) : (soundblaster_base_port + 0x0E);
|
||||
|
||||
// Set up the IRQ-driven ring buffer.
|
||||
hidden->chunk_size = device->buffer_size; // one DMA half-buffer
|
||||
|
||||
// Ring size must be a power of two and hold RING_BUFFER_CHUNKS chunks.
|
||||
hidden->ring_size = hidden->chunk_size * RING_BUFFER_CHUNKS;
|
||||
// Ensure power-of-two (chunk_size itself comes from SDL and may not be).
|
||||
{
|
||||
int rs = hidden->ring_size;
|
||||
rs--;
|
||||
rs |= rs >> 1;
|
||||
rs |= rs >> 2;
|
||||
rs |= rs >> 4;
|
||||
rs |= rs >> 8;
|
||||
rs |= rs >> 16;
|
||||
rs++;
|
||||
hidden->ring_size = rs;
|
||||
}
|
||||
|
||||
hidden->ring_buffer = (Uint8 *)SDL_calloc(1, hidden->ring_size);
|
||||
if (!hidden->ring_buffer) {
|
||||
return SDL_SetError("Couldn't allocate ring buffer for IRQ-driven audio");
|
||||
}
|
||||
hidden->staging_buffer = (Uint8 *)SDL_calloc(1, hidden->chunk_size);
|
||||
if (!hidden->staging_buffer) {
|
||||
return SDL_SetError("Couldn't allocate staging buffer for IRQ-driven audio");
|
||||
}
|
||||
|
||||
hidden->ring_read = 0;
|
||||
hidden->ring_write = 0;
|
||||
|
||||
// Populate ISR-visible statics (all will be memory-locked below).
|
||||
isr_ring_buffer = hidden->ring_buffer;
|
||||
isr_ring_read = 0;
|
||||
isr_ring_write = 0;
|
||||
isr_ring_size = hidden->ring_size;
|
||||
isr_ring_mask = hidden->ring_size - 1;
|
||||
isr_chunk_size = hidden->chunk_size;
|
||||
isr_dma_buffer = hidden->dma_buffer;
|
||||
isr_dma_halfdma = hidden->dma_buflen / 2;
|
||||
isr_dma_channel = hidden->dma_channel;
|
||||
isr_is_16bit = is_sb16;
|
||||
isr_silence_value = soundblaster_silence_value;
|
||||
|
||||
// Lock all ISR code and data to prevent page faults during interrupts.
|
||||
DOS_LockCode(SoundBlasterIRQHandler, SoundBlasterIRQHandler_End);
|
||||
DOS_LockCode(RingCopyOut, RingCopyOut_End);
|
||||
DOS_LockCode(ISR_GetDMAHalf, ISR_GetDMAHalf_End);
|
||||
DOS_LockData(*hidden->ring_buffer, hidden->ring_size);
|
||||
DOS_LockVariable(isr_ring_read);
|
||||
DOS_LockVariable(isr_ring_write);
|
||||
DOS_LockVariable(isr_ring_size);
|
||||
DOS_LockVariable(isr_ring_mask);
|
||||
DOS_LockVariable(isr_chunk_size);
|
||||
DOS_LockVariable(isr_ring_buffer);
|
||||
DOS_LockVariable(isr_dma_buffer);
|
||||
DOS_LockVariable(isr_dma_halfdma);
|
||||
DOS_LockVariable(isr_dma_channel);
|
||||
DOS_LockVariable(isr_is_16bit);
|
||||
DOS_LockVariable(isr_silence_value);
|
||||
DOS_LockVariable(isr_irq_ack_port);
|
||||
DOS_LockVariable(soundblaster_irq);
|
||||
|
||||
DOS_HookInterrupt(soundblaster_irq, SoundBlasterIRQHandler, &hidden->interrupt_hook);
|
||||
|
||||
WriteSoundBlasterDSP(0xD1); // turn on the speaker
|
||||
// The speaker-on command takes up to 112 ms to complete on real hardware.
|
||||
// Poll the DSP write status port (bit 7 clears when the DSP is ready);
|
||||
// in practice — and always in DOSBox — it completes almost instantly.
|
||||
{
|
||||
const int status_port = soundblaster_base_port + 0xC;
|
||||
const Uint64 deadline = SDL_GetTicksNS() + SDL_MS_TO_NS(112);
|
||||
while ((inportb(status_port) & 0x80) && (SDL_GetTicksNS() < deadline)) {
|
||||
SDL_DelayPrecise(SDL_US_TO_NS(100)); // brief yield between polls
|
||||
}
|
||||
}
|
||||
|
||||
if (is_sb16) {
|
||||
// SB16 (DSP >= 4): set output sample rate directly
|
||||
WriteSoundBlasterDSP(0x41); // set output sampling rate
|
||||
WriteSoundBlasterDSP((Uint8)(device->spec.freq >> 8));
|
||||
WriteSoundBlasterDSP((Uint8)(device->spec.freq & 0xFF));
|
||||
|
||||
// start 16-bit auto-initialize DMA mode
|
||||
// half the total buffer per transfer, then convert to samples (divide by 2 because they are 16-bits each).
|
||||
const int block_size = ((hidden->dma_buflen / 2) / sizeof(Sint16)) - 1; // one less than samples to be transferred.
|
||||
WriteSoundBlasterDSP(0xB6); // 16-bit output, auto-init, FIFO on
|
||||
WriteSoundBlasterDSP(0x30); // 16-bit stereo signed PCM
|
||||
WriteSoundBlasterDSP((Uint8)(block_size & 0xFF));
|
||||
WriteSoundBlasterDSP((Uint8)(block_size >> 8));
|
||||
} else {
|
||||
// Pre-SB16 (DSP < 4): set sample rate via Time Constant
|
||||
// Time Constant = 256 - (1000000 / (channels * freq))
|
||||
// In stereo mode the SB Pro interleaves L/R samples, so the effective
|
||||
// hardware rate is channels * freq.
|
||||
const int effective_rate = device->spec.channels * device->spec.freq;
|
||||
const Uint8 time_constant = (Uint8)(256 - (1000000 / effective_rate));
|
||||
WriteSoundBlasterDSP(0x40); // set time constant
|
||||
WriteSoundBlasterDSP(time_constant);
|
||||
|
||||
// SB Pro (DSP 3.x): enable or disable stereo via mixer register 0x0E
|
||||
if (soundblaster_version >= 3) {
|
||||
const int mixer_addr = soundblaster_base_port + 0x04;
|
||||
const int mixer_data = soundblaster_base_port + 0x05;
|
||||
outportb(mixer_addr, 0x0E); // select output/stereo register
|
||||
if (device->spec.channels == 2) {
|
||||
outportb(mixer_data, inportb(mixer_data) | 0x02); // set bit 1 = stereo
|
||||
} else {
|
||||
outportb(mixer_data, inportb(mixer_data) & ~0x02); // clear bit 1 = mono
|
||||
}
|
||||
}
|
||||
|
||||
// start 8-bit auto-initialize DMA mode
|
||||
// block_size is in bytes for 8-bit, and it's the half-buffer size minus 1
|
||||
const int block_size = (hidden->dma_buflen / 2) - 1;
|
||||
WriteSoundBlasterDSP(0x48); // set DSP block transfer size
|
||||
WriteSoundBlasterDSP((Uint8)(block_size & 0xFF));
|
||||
WriteSoundBlasterDSP((Uint8)(block_size >> 8));
|
||||
// NOTE: DSP 1.x does not support auto-init (0x1C). Those cards are extremely
|
||||
// rare and would need single-cycle transfers re-triggered from the ISR.
|
||||
// For now we use 0x1C anyway and hope for the best on DSP 1.x hardware.
|
||||
WriteSoundBlasterDSP(0x1C); // 8-bit auto-init DMA playback
|
||||
}
|
||||
|
||||
SDL_Log("SoundBlaster opened!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the staging buffer. The SDL audio pipeline writes the mixed audio
|
||||
// here; PlayDevice then copies it into the ring buffer.
|
||||
static Uint8 *DOSSOUNDBLASTER_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
(void)buffer_size; // unchanged, always one chunk
|
||||
return hidden->staging_buffer;
|
||||
}
|
||||
|
||||
// Commit the staging buffer into the ring buffer.
|
||||
// Called by SDL's audio thread after it has written a full chunk.
|
||||
static bool DOSSOUNDBLASTER_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const int mask = hidden->ring_size - 1;
|
||||
const int pos = hidden->ring_write & mask;
|
||||
const int first = (pos + buffer_size <= hidden->ring_size) ? buffer_size : (hidden->ring_size - pos);
|
||||
|
||||
SDL_memcpy(hidden->ring_buffer + pos, buffer, first);
|
||||
if (first < buffer_size) {
|
||||
SDL_memcpy(hidden->ring_buffer, buffer + first, buffer_size - first);
|
||||
}
|
||||
|
||||
// Advance the write cursor. Interrupts are disabled around the store so
|
||||
// the ISR never sees a torn write (not strictly necessary on x86 for an
|
||||
// aligned int, but let's be safe).
|
||||
DOS_DisableInterrupts();
|
||||
hidden->ring_write += buffer_size;
|
||||
isr_ring_write = hidden->ring_write;
|
||||
DOS_EnableInterrupts();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DOSSOUNDBLASTER_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
if (hidden) {
|
||||
// Disable PCM.
|
||||
if (hidden->is_16bit) {
|
||||
WriteSoundBlasterDSP(0xDA); // exit 16-bit auto-init DMA
|
||||
WriteSoundBlasterDSP(0xD3); // turn off the speaker
|
||||
} else {
|
||||
WriteSoundBlasterDSP(0xD0); // halt 8-bit DMA
|
||||
WriteSoundBlasterDSP(0xDA); // exit auto-init DMA
|
||||
WriteSoundBlasterDSP(0xD3); // turn off the speaker
|
||||
|
||||
// SB Pro: reset stereo bit in mixer register 0x0E
|
||||
if (soundblaster_version >= 3) {
|
||||
const int mixer_addr = soundblaster_base_port + 0x04;
|
||||
const int mixer_data = soundblaster_base_port + 0x05;
|
||||
outportb(mixer_addr, 0x0E);
|
||||
outportb(mixer_data, inportb(mixer_data) & ~0x02); // clear stereo bit
|
||||
}
|
||||
}
|
||||
|
||||
DOS_UnhookInterrupt(&hidden->interrupt_hook, true);
|
||||
|
||||
// disable DMA — mask the appropriate DMA channel.
|
||||
if (hidden->dma_buffer) {
|
||||
if (hidden->is_16bit) {
|
||||
outportb(0xD4, 0x04 | hidden->dma_channel); // mask high DMA channel (channels 5-7)
|
||||
} else {
|
||||
outportb(0x0A, 0x04 | hidden->dma_channel); // mask low DMA channel (channels 0-3)
|
||||
}
|
||||
DOS_FreeConventionalMemory(&hidden->dma_seginfo);
|
||||
}
|
||||
|
||||
// Free ring buffer resources.
|
||||
if (hidden->ring_buffer) {
|
||||
SDL_free(hidden->ring_buffer);
|
||||
}
|
||||
if (hidden->staging_buffer) {
|
||||
SDL_free(hidden->staging_buffer);
|
||||
}
|
||||
|
||||
// Clear ISR-visible statics.
|
||||
isr_ring_buffer = NULL;
|
||||
isr_ring_read = 0;
|
||||
isr_ring_write = 0;
|
||||
isr_ring_size = 0;
|
||||
isr_ring_mask = 0;
|
||||
isr_chunk_size = 0;
|
||||
isr_dma_buffer = NULL;
|
||||
isr_dma_halfdma = 0;
|
||||
isr_irq_ack_port = 0;
|
||||
|
||||
SDL_free(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckForSoundBlaster(void)
|
||||
{
|
||||
ResetSoundBlasterDSP();
|
||||
|
||||
// wait for the DSP to say it's ready.
|
||||
bool ready = false;
|
||||
for (int i = 0; i < 300; i++) { // may take up to 100msecs to initialize. We'll give it 300.
|
||||
SDL_DelayPrecise(1000);
|
||||
if (ReadSoundBlasterReady()) {
|
||||
ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
return SDL_SetError("No SoundBlaster detected on port 0x%X", soundblaster_base_port); // either no SoundBlaster or it's on a different base port.
|
||||
} else if (ReadSoundBlasterDSP() != 0xAA) {
|
||||
return SDL_SetError("Not a SoundBlaster at port 0x%X", soundblaster_base_port); // either it's not a SoundBlaster or there's a problem.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsSoundBlasterPresent(void)
|
||||
{
|
||||
const char *env = SDL_getenv("BLASTER");
|
||||
if (!env) {
|
||||
return SDL_SetError("No BLASTER environment variable to find Sound Blaster"); // definitely doesn't have a Sound Blaster (or they screwed up).
|
||||
}
|
||||
|
||||
char *copy = SDL_strdup(env);
|
||||
if (!copy) {
|
||||
return false; // oh well.
|
||||
}
|
||||
|
||||
char *str = copy;
|
||||
char *saveptr = NULL;
|
||||
|
||||
char *token;
|
||||
while ((token = SDL_strtok_r(str, " ", &saveptr)) != NULL) {
|
||||
str = NULL; // must be NULL for future calls to tokenize the same string.
|
||||
char *endp = NULL;
|
||||
const int base = (SDL_toupper(*token) == 'A') ? 16 : 10;
|
||||
const int num = (int)SDL_strtol(token + 1, &endp, base);
|
||||
if ((token[1] == 0) || (*endp != 0)) { // bogus num
|
||||
continue;
|
||||
} else if (num < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (SDL_toupper(*token)) {
|
||||
case 'A': // Base i/o port (in hex)
|
||||
soundblaster_base_port = num;
|
||||
break;
|
||||
|
||||
case 'I': // IRQ
|
||||
soundblaster_irq = num;
|
||||
break;
|
||||
|
||||
case 'D': // DMA channel
|
||||
soundblaster_dma_channel = num;
|
||||
break;
|
||||
|
||||
case 'H': // High DMA channel
|
||||
soundblaster_highdma_channel = num;
|
||||
break;
|
||||
|
||||
// don't care about these.
|
||||
// case 'M': // mixer chip base port
|
||||
// case 'P': // MPU-401 base port
|
||||
// case 'T': // type of device
|
||||
// case 'E': // EMU8000 base port: an AWE32 thing
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_free(copy);
|
||||
|
||||
if (soundblaster_base_port < 0 || soundblaster_irq < 0 || (soundblaster_dma_channel < 0 && soundblaster_highdma_channel < 0)) {
|
||||
return SDL_SetError("BLASTER environment variable is incomplete or incorrect");
|
||||
} else if (!CheckForSoundBlaster()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteSoundBlasterDSP(0xE1); // query DSP version
|
||||
soundblaster_version = (int)ReadSoundBlasterDSP();
|
||||
soundblaster_version_minor = (int)ReadSoundBlasterDSP();
|
||||
|
||||
SDL_Log("SB: BLASTER env='%s'", env);
|
||||
SDL_Log("SB: port=0x%X", soundblaster_base_port);
|
||||
SDL_Log("SB: irq=%d", soundblaster_irq);
|
||||
SDL_Log("SB: dma8=%d", soundblaster_dma_channel);
|
||||
SDL_Log("SB: dma16=%d", soundblaster_highdma_channel);
|
||||
SDL_Log("SB: version=%d.%d", soundblaster_version, soundblaster_version_minor);
|
||||
|
||||
soundblaster_is_sb16 = !FORCE_SB_8BIT && (soundblaster_version >= 4);
|
||||
soundblaster_silence_value = soundblaster_is_sb16 ? 0x00 : 0x80; // S16LE silence is 0x00, U8 silence is 0x80
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DOSSOUNDBLASTER_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!IsSoundBlasterPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->OpenDevice = DOSSOUNDBLASTER_OpenDevice;
|
||||
impl->WaitDevice = DOSSOUNDBLASTER_WaitDevice;
|
||||
impl->GetDeviceBuf = DOSSOUNDBLASTER_GetDeviceBuf;
|
||||
impl->PlayDevice = DOSSOUNDBLASTER_PlayDevice;
|
||||
impl->CloseDevice = DOSSOUNDBLASTER_CloseDevice;
|
||||
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DOSSOUNDBLASTER_bootstrap = {
|
||||
"soundblaster", "Sound Blaster", DOSSOUNDBLASTER_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER
|
||||
47
src/audio/dos/SDL_dosaudio_sb.h
Normal file
47
src/audio/dos/SDL_dosaudio_sb.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dosaudio_sb_h_
|
||||
#define SDL_dosaudio_sb_h_
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *dma_buffer;
|
||||
size_t dma_buflen;
|
||||
int dma_channel;
|
||||
bool is_16bit;
|
||||
_go32_dpmi_seginfo dma_seginfo;
|
||||
DOS_InterruptHook interrupt_hook;
|
||||
|
||||
// IRQ-driven ring buffer
|
||||
Uint8 *ring_buffer; // pre-allocated, memory-locked ring buffer
|
||||
volatile int ring_read; // read position (advanced by IRQ handler only)
|
||||
volatile int ring_write; // write position (advanced by audio thread only)
|
||||
int ring_size; // total ring buffer size (power-of-2, multiple of chunk_size)
|
||||
int chunk_size; // == device->buffer_size (one DMA half-buffer worth)
|
||||
Uint8 *staging_buffer; // audio thread writes here, then commits to ring
|
||||
};
|
||||
|
||||
#endif // SDL_dosaudio_sb_h_
|
||||
142
src/core/dos/SDL_dos.c
Normal file
142
src/core/dos/SDL_dos.c
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_DOS)
|
||||
|
||||
#include "SDL_dos.h"
|
||||
|
||||
void *DOS_AllocateConventionalMemory(const int len, _go32_dpmi_seginfo *seginfo)
|
||||
{
|
||||
seginfo->size = (len + 15) / 16; // this is in "paragraphs"
|
||||
if (_go32_dpmi_allocate_dos_memory(seginfo) != 0) {
|
||||
SDL_OutOfMemory();
|
||||
return NULL;
|
||||
}
|
||||
// No need to lock: DPMI 0.9 §3.3 guarantees the first megabyte is always
|
||||
// committed and locked. CWSDPMI (the DJGPP DPMI host) doesn't support
|
||||
// virtual memory at all, so conventional memory is never paged out.
|
||||
return DOS_PhysicalToLinear(seginfo->rm_segment * 16);
|
||||
}
|
||||
|
||||
void *DOS_AllocateDMAMemory(const int len, _go32_dpmi_seginfo *seginfo)
|
||||
{
|
||||
// ISA DMA transfers cannot cross a 64 KB physical page boundary (hardware
|
||||
// limitation of the 8237 DMA controller). Allocating 2× the requested size
|
||||
// guarantees at least one contiguous `len`-byte region that doesn't straddle
|
||||
// a boundary. This is the standard technique used by Allegro, MIDAS, and
|
||||
// every other DOS audio library; allocate-check-retry would add complexity
|
||||
// for zero benefit.
|
||||
uint8_t *ptr = (uint8_t *)DOS_AllocateConventionalMemory(len * 2, seginfo);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// if we're past the end of a page, use the second half of the block.
|
||||
const uint32_t physical = (seginfo->rm_segment * 16);
|
||||
if ((physical >> 16) != ((physical + len) >> 16)) {
|
||||
ptr += len;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void DOS_FreeConventionalMemory(_go32_dpmi_seginfo *seginfo)
|
||||
{
|
||||
_go32_dpmi_free_dos_memory(seginfo);
|
||||
}
|
||||
|
||||
char *DOS_GetFarPtrCString(const Uint32 segoffset)
|
||||
{
|
||||
if (!segoffset) { // let's just treat this as a NULL pointer.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned long ofs = (unsigned long)(((segoffset & 0xFFFF0000) >> 12) + (segoffset & 0xFFFF));
|
||||
size_t len;
|
||||
|
||||
for (len = 0; _farpeekb(_dos_ds, ofs + len) != '\0'; len++) {
|
||||
}
|
||||
|
||||
len++; // null terminator.
|
||||
char *retval = SDL_malloc(len);
|
||||
if (!retval) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
retval[i] = (char)_farpeekb(_dos_ds, ofs + i);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void DOS_HookInterrupt(int irq, DOS_InterruptHookFn fn, DOS_InterruptHook *hook)
|
||||
{
|
||||
SDL_assert(irq >= 0 && irq <= 15);
|
||||
SDL_assert(fn != NULL);
|
||||
SDL_assert(hook != NULL);
|
||||
hook->fn = fn;
|
||||
hook->irq = irq;
|
||||
hook->interrupt_vector = DOS_IRQToVector(irq);
|
||||
hook->irq_handler_seginfo.pm_selector = _go32_my_cs();
|
||||
hook->irq_handler_seginfo.pm_offset = (uint32_t)fn;
|
||||
_go32_dpmi_get_protected_mode_interrupt_vector(hook->interrupt_vector, &hook->original_irq_handler_seginfo);
|
||||
_go32_dpmi_chain_protected_mode_interrupt_vector(hook->interrupt_vector, &hook->irq_handler_seginfo);
|
||||
|
||||
// enable interrupt on the correct PIC
|
||||
if (irq > 7) {
|
||||
outportb(PIC2_DATA, inportb(PIC2_DATA) & ~(1 << (irq - 8))); // unmask on slave PIC
|
||||
outportb(PIC1_DATA, inportb(PIC1_DATA) & ~(1 << 2)); // ensure cascade (IRQ2) is unmasked
|
||||
} else {
|
||||
outportb(PIC1_DATA, inportb(PIC1_DATA) & ~(1 << irq)); // unmask on master PIC
|
||||
}
|
||||
}
|
||||
|
||||
void DOS_UnhookInterrupt(DOS_InterruptHook *hook, bool disable_interrupt)
|
||||
{
|
||||
if (!hook || !hook->fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_assert(hook->interrupt_vector > 0);
|
||||
SDL_assert(hook->irq > 0);
|
||||
|
||||
if (disable_interrupt) {
|
||||
if (hook->irq > 7) {
|
||||
outportb(PIC2_DATA, inportb(PIC2_DATA) | (1 << (hook->irq - 8))); // mask on slave PIC
|
||||
} else {
|
||||
outportb(PIC1_DATA, inportb(PIC1_DATA) | (1 << hook->irq)); // mask on master PIC
|
||||
}
|
||||
}
|
||||
|
||||
_go32_dpmi_set_protected_mode_interrupt_vector(hook->interrupt_vector, &hook->original_irq_handler_seginfo);
|
||||
|
||||
// Note: _go32_dpmi_chain_protected_mode_interrupt_vector internally
|
||||
// allocates a wrapper, but it is NOT safe to free it with
|
||||
// _go32_dpmi_free_iret_wrapper — that function expects a wrapper
|
||||
// allocated by _go32_dpmi_allocate_iret_wrapper, and calling it on
|
||||
// a chain-allocated wrapper causes a page fault on exit. The wrapper
|
||||
// is a few bytes of conventional memory that the OS reclaims when the
|
||||
// process terminates, so the leak is harmless.
|
||||
|
||||
SDL_zerop(hook);
|
||||
}
|
||||
|
||||
#endif // defined(SDL_PLATFORM_DOS)
|
||||
156
src/core/dos/SDL_dos.h
Normal file
156
src/core/dos/SDL_dos.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dos_h_
|
||||
#define SDL_dos_h_
|
||||
|
||||
#include <sys/farptr.h>
|
||||
|
||||
// our MS-DOS port depends on the "fat DS" trick. djgpp docs try to warn you
|
||||
// away from this, but if it was good enough for Quake 1, it's good enough
|
||||
// for us!
|
||||
#include <sys/nearptr.h>
|
||||
|
||||
// We are obviously a 32-bit protected mode (DPMI) program.
|
||||
#include <dpmi.h>
|
||||
|
||||
// this is djgpp-specific.
|
||||
#include <go32.h>
|
||||
|
||||
// this is DOS PC stuff, like interrupts and Intel i/o ports.
|
||||
#include <pc.h>
|
||||
|
||||
// 8259 PIC (Programmable Interrupt Controller) ports and commands
|
||||
#define PIC1_COMMAND 0x20 // master PIC command port
|
||||
#define PIC1_DATA 0x21 // master PIC data (mask) port
|
||||
#define PIC2_COMMAND 0xA0 // slave PIC command port
|
||||
#define PIC2_DATA 0xA1 // slave PIC data (mask) port
|
||||
#define PIC_EOI 0x20 // end-of-interrupt command
|
||||
|
||||
// Lock a range of code so it won't be paged out during interrupts.
|
||||
// Usage: DOS_LockCode(function_name, function_end_label)
|
||||
// The function_end_label must be defined immediately after the function.
|
||||
#define DOS_LockCode(start, end) \
|
||||
_go32_dpmi_lock_code((void *)(start), (char *)(end) - (char *)(start))
|
||||
|
||||
// Lock a range of data so it won't be paged out during interrupts.
|
||||
#define DOS_LockData(var, size) \
|
||||
_go32_dpmi_lock_data((void *)&(var), (size))
|
||||
|
||||
// Lock a single variable.
|
||||
#define DOS_LockVariable(var) \
|
||||
DOS_LockData(var, sizeof(var))
|
||||
|
||||
// Set up for C function definitions, even when using C++
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// This uses the "fat DS" trick to convert a physical address to a valid
|
||||
// C pointer usable from protected mode.
|
||||
SDL_FORCE_INLINE void *DOS_PhysicalToLinear(const Uint32 physical)
|
||||
{
|
||||
__djgpp_nearptr_enable(); // We need to re-enable this for large applications to work.
|
||||
return (void *)(physical + __djgpp_conventional_base);
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE Uint32 DOS_LinearToPhysical(void *linear)
|
||||
{
|
||||
return ((Uint32)linear) - __djgpp_conventional_base;
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE int DOS_IRQToVector(int irq)
|
||||
{
|
||||
return irq + ((irq > 7) ? 104 : 8);
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE void DOS_DisableInterrupts(void)
|
||||
{
|
||||
__asm__ __volatile__("cli\n");
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE void DOS_EnableInterrupts(void)
|
||||
{
|
||||
__asm__ __volatile__("sti\n");
|
||||
}
|
||||
|
||||
// Grab a single byte from a segment:offset.
|
||||
SDL_FORCE_INLINE Uint8 DOS_PeekUint8(const Uint32 segoffset)
|
||||
{
|
||||
return (Uint8)_farpeekb(_dos_ds, ((segoffset & 0xFFFF0000) >> 12) + (segoffset & 0xFFFF));
|
||||
}
|
||||
|
||||
// Grab a single 16-bit word from a segment:offset.
|
||||
SDL_FORCE_INLINE Uint16 DOS_PeekUint16(const Uint32 segoffset)
|
||||
{
|
||||
return (Uint16)_farpeekw(_dos_ds, ((segoffset & 0xFFFF0000) >> 12) + (segoffset & 0xFFFF));
|
||||
}
|
||||
|
||||
// Grab a single 32-bit dword from a segment:offset.
|
||||
SDL_FORCE_INLINE Uint32 DOS_PeekUint32(const Uint32 segoffset)
|
||||
{
|
||||
return (Uint32)_farpeekl(_dos_ds, ((segoffset & 0xFFFF0000) >> 12) + (segoffset & 0xFFFF));
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE void DOS_EndOfInterrupt(int irq)
|
||||
{
|
||||
if (irq > 7) {
|
||||
outportb(PIC2_COMMAND, PIC_EOI);
|
||||
}
|
||||
outportb(PIC1_COMMAND, PIC_EOI);
|
||||
}
|
||||
|
||||
// Allocate memory under the 640k line; various real mode services and DMA transfers need this.
|
||||
// malloc() returns data above 640k because we're a protected mode, 32-bit process, so this is
|
||||
// only for specific needs.
|
||||
extern void *DOS_AllocateConventionalMemory(const int len, _go32_dpmi_seginfo *seginfo);
|
||||
|
||||
// Allocate conventional memory suitable for DMA transfers.
|
||||
extern void *DOS_AllocateDMAMemory(const int len, _go32_dpmi_seginfo *seginfo);
|
||||
|
||||
// Free conventional (or DMA, which _is_ conventional) memory.
|
||||
extern void DOS_FreeConventionalMemory(_go32_dpmi_seginfo *seginfo);
|
||||
|
||||
// Get a SDL_malloc'd copy of a null-terminated string located at a real-mode segment:offset. This makes no promises about character encoding.
|
||||
char *DOS_GetFarPtrCString(const Uint32 segoffset);
|
||||
|
||||
typedef void (*DOS_InterruptHookFn)(void);
|
||||
typedef struct DOS_InterruptHook
|
||||
{
|
||||
DOS_InterruptHookFn fn;
|
||||
int irq;
|
||||
int interrupt_vector; // this is the _vector_ number, not the IRQ number!
|
||||
_go32_dpmi_seginfo irq_handler_seginfo;
|
||||
_go32_dpmi_seginfo original_irq_handler_seginfo;
|
||||
|
||||
} DOS_InterruptHook;
|
||||
|
||||
void DOS_HookInterrupt(int irq, DOS_InterruptHookFn fn, DOS_InterruptHook *hook); // `irq` is the IRQ number, not the interrupt vector number!
|
||||
void DOS_UnhookInterrupt(DOS_InterruptHook *hook, bool disable_interrupt);
|
||||
|
||||
// Ends C function definitions when using C++
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_dos_h_
|
||||
318
src/core/dos/SDL_dos_scheduler.c
Normal file
318
src/core/dos/SDL_dos_scheduler.c
Normal file
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
|
||||
#include "SDL_dos.h"
|
||||
#include "SDL_dos_scheduler.h"
|
||||
#include <setjmp.h>
|
||||
|
||||
/* DJGPP's jmp_buf is defined as:
|
||||
typedef struct __jmp_buf {
|
||||
unsigned long __eax, __ebx, __ecx, __edx, __esi;
|
||||
unsigned long __edi, __ebp, __esp, __eip, __eflags;
|
||||
unsigned short __cs, __ds, __es, __fs, __gs, __ss;
|
||||
unsigned long __sigmask, __signum, __exception_ptr;
|
||||
unsigned char __fpu_state[108];
|
||||
} jmp_buf[1];
|
||||
We patch __esp, __ebp, and __eip to bootstrap new thread contexts. */
|
||||
|
||||
// Thread table — static array, no dynamic allocation needed
|
||||
static DOS_ThreadContext threads[DOS_MAX_THREADS];
|
||||
static int current_thread = 0; // Index of currently running thread
|
||||
static bool scheduler_initialized = false;
|
||||
|
||||
// Find the next runnable thread using round-robin scheduling.
|
||||
// Returns thread ID, or -1 if no other thread is runnable.
|
||||
static int FindNextRunnable(int start)
|
||||
{
|
||||
for (int i = 1; i <= DOS_MAX_THREADS; i++) {
|
||||
int idx = (start + i) % DOS_MAX_THREADS;
|
||||
if (threads[idx].state == DOS_THREAD_READY) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return -1; // No other thread is runnable
|
||||
}
|
||||
|
||||
// Trampoline function that runs on a new thread's stack.
|
||||
// This is jumped to from DOS_Yield when the new thread runs for the first time.
|
||||
// We use a global to pass the thread ID since we just switched stacks.
|
||||
static volatile int trampoline_thread_id;
|
||||
|
||||
static void ThreadTrampoline(void)
|
||||
{
|
||||
int tid = trampoline_thread_id;
|
||||
DOS_ThreadContext *ctx = &threads[tid];
|
||||
|
||||
// Run the user's thread function
|
||||
int result = ctx->entry_fn(ctx->entry_arg);
|
||||
|
||||
// Thread is done
|
||||
DOS_ExitThread(result);
|
||||
|
||||
// Should never reach here
|
||||
for (;;) {
|
||||
}
|
||||
}
|
||||
|
||||
void DOS_SchedulerInit(void)
|
||||
{
|
||||
if (scheduler_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_memset(threads, 0, sizeof(threads));
|
||||
|
||||
// Thread 0 is the main thread (already running)
|
||||
threads[0].state = DOS_THREAD_RUNNING;
|
||||
threads[0].id = 0;
|
||||
threads[0].join_waiter = -1;
|
||||
current_thread = 0;
|
||||
|
||||
scheduler_initialized = true;
|
||||
|
||||
_go32_dpmi_lock_data((void *)threads, sizeof(threads));
|
||||
_go32_dpmi_lock_data((void *)¤t_thread, sizeof(current_thread));
|
||||
_go32_dpmi_lock_data((void *)&scheduler_initialized, sizeof(scheduler_initialized));
|
||||
_go32_dpmi_lock_data((void *)&trampoline_thread_id, sizeof(trampoline_thread_id));
|
||||
}
|
||||
|
||||
void DOS_SchedulerQuit(void)
|
||||
{
|
||||
// Clean up any remaining threads
|
||||
for (int i = 1; i < DOS_MAX_THREADS; i++) {
|
||||
if (threads[i].state != DOS_THREAD_FREE) {
|
||||
SDL_assert(threads[i].state == DOS_THREAD_FINISHED);
|
||||
DOS_DestroyThread(i);
|
||||
}
|
||||
}
|
||||
|
||||
scheduler_initialized = false;
|
||||
}
|
||||
|
||||
int DOS_CreateThread(int (*fn)(void *), void *arg, size_t stack_size)
|
||||
{
|
||||
if (!scheduler_initialized) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find a free slot
|
||||
int tid = -1;
|
||||
for (int i = 1; i < DOS_MAX_THREADS; i++) {
|
||||
if (threads[i].state == DOS_THREAD_FREE) {
|
||||
tid = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tid < 0) {
|
||||
return -1; // No free slots
|
||||
}
|
||||
|
||||
if (stack_size == 0) {
|
||||
stack_size = DOS_DEFAULT_STACK_SIZE;
|
||||
}
|
||||
|
||||
// Allocate stack
|
||||
void *stack = SDL_malloc(stack_size);
|
||||
if (!stack) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Lock the stack memory so it won't be paged out.
|
||||
// This is important for context switches — we can't take a page fault
|
||||
// while switching stacks.
|
||||
_go32_dpmi_lock_data(stack, stack_size);
|
||||
|
||||
DOS_ThreadContext *ctx = &threads[tid];
|
||||
SDL_memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->id = tid;
|
||||
ctx->state = DOS_THREAD_READY;
|
||||
ctx->stack_base = stack;
|
||||
ctx->stack_size = stack_size;
|
||||
ctx->entry_fn = fn;
|
||||
ctx->entry_arg = arg;
|
||||
ctx->finished = false;
|
||||
ctx->join_waiter = -1;
|
||||
|
||||
// Set up the initial context. We use setjmp to save a template context,
|
||||
// then modify the stack pointer to point to our new stack.
|
||||
//
|
||||
// The trick: we setjmp here to capture register state, then manually
|
||||
// patch the saved __esp and __eip in the jmp_buf struct to point to
|
||||
// our new stack and trampoline function.
|
||||
if (setjmp(ctx->env) == 0) {
|
||||
// Patch the saved context to use our new stack and trampoline.
|
||||
// Stack grows downward, so SP starts at the top.
|
||||
// Align to 16 bytes for ABI compliance.
|
||||
Uint8 *stack_top = (Uint8 *)stack + stack_size;
|
||||
stack_top = (Uint8 *)((uintptr_t)stack_top & ~0xFUL); // 16-byte align
|
||||
|
||||
// Leave room for a fake return address (the trampoline never returns,
|
||||
// but the ABI expects one on the stack at function entry)
|
||||
stack_top -= sizeof(void *);
|
||||
*(void **)stack_top = NULL; // Fake return address
|
||||
|
||||
ctx->env[0].__esp = (unsigned long)(uintptr_t)stack_top;
|
||||
ctx->env[0].__ebp = (unsigned long)(uintptr_t)stack_top;
|
||||
ctx->env[0].__eip = (unsigned long)(uintptr_t)ThreadTrampoline;
|
||||
} else {
|
||||
SDL_assert(!"Unreachable");
|
||||
}
|
||||
|
||||
return tid;
|
||||
}
|
||||
|
||||
void DOS_Yield(void)
|
||||
{
|
||||
if (!scheduler_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
int next = FindNextRunnable(current_thread);
|
||||
if (next < 0) {
|
||||
return; // No other runnable thread, continue current
|
||||
}
|
||||
|
||||
int prev = current_thread;
|
||||
|
||||
// Save current context and switch
|
||||
if (setjmp(threads[prev].env) == 0) {
|
||||
// Mark previous thread as READY (unless it's BLOCKED or FINISHED)
|
||||
if (threads[prev].state == DOS_THREAD_RUNNING) {
|
||||
threads[prev].state = DOS_THREAD_READY;
|
||||
}
|
||||
|
||||
// Switch to next thread
|
||||
current_thread = next;
|
||||
threads[next].state = DOS_THREAD_RUNNING;
|
||||
|
||||
// For new threads that haven't run yet, set the trampoline ID
|
||||
trampoline_thread_id = next;
|
||||
|
||||
longjmp(threads[next].env, 1);
|
||||
}
|
||||
// else: we've been switched back to — just return
|
||||
}
|
||||
|
||||
void DOS_ExitThread(int status)
|
||||
{
|
||||
DOS_ThreadContext *ctx = &threads[current_thread];
|
||||
ctx->exit_status = status;
|
||||
ctx->finished = true;
|
||||
ctx->state = DOS_THREAD_FINISHED;
|
||||
|
||||
// Wake up anyone waiting to join this thread
|
||||
if (ctx->join_waiter >= 0 && ctx->join_waiter < DOS_MAX_THREADS) {
|
||||
DOS_WakeThread(ctx->join_waiter);
|
||||
}
|
||||
|
||||
// Find another thread to run. We can't return — our stack frame
|
||||
// belongs to the thread that just exited.
|
||||
int next = FindNextRunnable(current_thread);
|
||||
if (next >= 0) {
|
||||
current_thread = next;
|
||||
threads[next].state = DOS_THREAD_RUNNING;
|
||||
trampoline_thread_id = next;
|
||||
longjmp(threads[next].env, 1);
|
||||
}
|
||||
|
||||
// If no other thread is runnable and we're not the main thread,
|
||||
// this is a problem. Spin-wait for someone to become runnable.
|
||||
// (This shouldn't happen in practice — the main thread should
|
||||
// always be runnable or waiting.)
|
||||
for (;;) {
|
||||
__asm__ __volatile__("hlt"); // Wait for interrupt before retrying
|
||||
next = FindNextRunnable(current_thread);
|
||||
if (next >= 0) {
|
||||
current_thread = next;
|
||||
threads[next].state = DOS_THREAD_RUNNING;
|
||||
trampoline_thread_id = next;
|
||||
longjmp(threads[next].env, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DOS_JoinThread(int thread_id)
|
||||
{
|
||||
if (thread_id < 0 || thread_id >= DOS_MAX_THREADS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
DOS_ThreadContext *target = &threads[thread_id];
|
||||
|
||||
// If already finished, just return the status
|
||||
if (target->finished) {
|
||||
return target->exit_status;
|
||||
}
|
||||
|
||||
// Register ourselves as the join waiter
|
||||
target->join_waiter = current_thread;
|
||||
|
||||
// Block until the target thread finishes
|
||||
while (!target->finished) {
|
||||
DOS_BlockCurrentThread();
|
||||
}
|
||||
|
||||
return target->exit_status;
|
||||
}
|
||||
|
||||
int DOS_GetCurrentThreadID(void)
|
||||
{
|
||||
if (!scheduler_initialized) {
|
||||
return 1;
|
||||
}
|
||||
return current_thread + 1;
|
||||
}
|
||||
|
||||
void DOS_WakeThread(int thread_id)
|
||||
{
|
||||
if (thread_id >= 0 && thread_id < DOS_MAX_THREADS) {
|
||||
if (threads[thread_id].state == DOS_THREAD_BLOCKED) {
|
||||
threads[thread_id].state = DOS_THREAD_READY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DOS_BlockCurrentThread(void)
|
||||
{
|
||||
threads[current_thread].state = DOS_THREAD_BLOCKED;
|
||||
DOS_Yield();
|
||||
}
|
||||
|
||||
void DOS_DestroyThread(int thread_id)
|
||||
{
|
||||
if (thread_id <= 0 || thread_id >= DOS_MAX_THREADS) {
|
||||
return; // Can't destroy main thread (0) or invalid IDs
|
||||
}
|
||||
|
||||
DOS_ThreadContext *ctx = &threads[thread_id];
|
||||
if (ctx->stack_base) {
|
||||
SDL_free(ctx->stack_base);
|
||||
ctx->stack_base = NULL;
|
||||
}
|
||||
ctx->state = DOS_THREAD_FREE;
|
||||
}
|
||||
|
||||
#endif // SDL_PLATFORM_DOS
|
||||
107
src/core/dos/SDL_dos_scheduler.h
Normal file
107
src/core/dos/SDL_dos_scheduler.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_dos_scheduler_h_
|
||||
#define SDL_dos_scheduler_h_
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Maximum number of cooperative threads. DOS doesn't need many —
|
||||
// typically just main thread + audio thread + maybe a loading thread.
|
||||
#define DOS_MAX_THREADS 16
|
||||
|
||||
// Default stack size for new threads (64 KB)
|
||||
#define DOS_DEFAULT_STACK_SIZE (64 * 1024)
|
||||
|
||||
// Thread states
|
||||
typedef enum
|
||||
{
|
||||
DOS_THREAD_FREE = 0, // Slot is available
|
||||
DOS_THREAD_READY, // Runnable
|
||||
DOS_THREAD_RUNNING, // Currently executing
|
||||
DOS_THREAD_BLOCKED, // Waiting on a semaphore/mutex
|
||||
DOS_THREAD_FINISHED // Thread function returned
|
||||
} DOS_ThreadState;
|
||||
|
||||
// Per-thread context
|
||||
typedef struct DOS_ThreadContext
|
||||
{
|
||||
jmp_buf env; // Saved CPU state for context switch
|
||||
DOS_ThreadState state;
|
||||
int id; // Thread ID (index into thread table)
|
||||
void *stack_base; // malloc'd stack memory
|
||||
size_t stack_size;
|
||||
int exit_status; // Return value from thread function
|
||||
|
||||
// Entry point
|
||||
int (*entry_fn)(void *);
|
||||
void *entry_arg;
|
||||
|
||||
// Join support
|
||||
volatile bool finished; // Set when thread function returns
|
||||
int join_waiter; // ID of thread waiting in WaitThread, or -1
|
||||
} DOS_ThreadContext;
|
||||
|
||||
// Initialize the scheduler. Must be called before any thread operations.
|
||||
// Registers the calling context as thread 0 (the main thread).
|
||||
void DOS_SchedulerInit(void);
|
||||
|
||||
// Shut down the scheduler.
|
||||
void DOS_SchedulerQuit(void);
|
||||
|
||||
// Create a new thread. Returns thread ID (>0) on success, -1 on failure.
|
||||
// The thread starts in READY state and will run when yielded to.
|
||||
int DOS_CreateThread(int (*fn)(void *), void *arg, size_t stack_size);
|
||||
|
||||
// Yield the current thread's timeslice. Switches to the next runnable thread
|
||||
// using round-robin scheduling. If no other thread is runnable, returns
|
||||
// immediately (no-op). This is the core cooperative scheduling primitive.
|
||||
void DOS_Yield(void);
|
||||
|
||||
// Mark a thread as finished and yield. Called when a thread's entry
|
||||
// function returns.
|
||||
void DOS_ExitThread(int status);
|
||||
|
||||
// Block until the specified thread finishes. Returns the thread's exit status.
|
||||
int DOS_JoinThread(int thread_id);
|
||||
|
||||
// Get the current thread's ID.
|
||||
int DOS_GetCurrentThreadID(void);
|
||||
|
||||
// Mark a thread as READY (used by semaphore signal to wake a blocked thread).
|
||||
void DOS_WakeThread(int thread_id);
|
||||
|
||||
// Mark the current thread as BLOCKED and yield (used by semaphore wait).
|
||||
void DOS_BlockCurrentThread(void);
|
||||
|
||||
// Destroy a thread's resources (stack, etc). Thread must be FINISHED or FREE.
|
||||
void DOS_DestroyThread(int thread_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_dos_scheduler_h_
|
||||
@@ -65,6 +65,8 @@
|
||||
#define SDL_DYNAMIC_API 0 // devkitARM doesn't support dynamic linking
|
||||
#elif defined(SDL_PLATFORM_NGAGE)
|
||||
#define SDL_DYNAMIC_API 0
|
||||
#elif defined(SDL_PLATFORM_DOS)
|
||||
#define SDL_DYNAMIC_API 0 // DJGPP doesn't support dynamic linking
|
||||
#elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN)
|
||||
#define SDL_DYNAMIC_API 0 // we need dlopen(), but don't have it....
|
||||
#endif
|
||||
|
||||
105
src/filesystem/dos/SDL_sysfilesystem.c
Normal file
105
src/filesystem/dos/SDL_sysfilesystem.c
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_FILESYSTEM_DOS
|
||||
|
||||
#include <dir.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
// System dependent filesystem routines
|
||||
|
||||
#include "../SDL_sysfilesystem.h"
|
||||
|
||||
char *SDL_SYS_GetBasePath(void)
|
||||
{
|
||||
extern const char *SDL_argv0; // from src/main/dos/SDL_sysmain_runapp.c
|
||||
char *searched = searchpath(SDL_argv0);
|
||||
if (!searched) {
|
||||
SDL_SetError("argv[0] not found by searchpath");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *fullpath = SDL_strdup(searched);
|
||||
if (!fullpath) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// I don't know if this is a good idea. Drop DOS path separators, use Unix style instead.
|
||||
char *ptr;
|
||||
for (ptr = fullpath; *ptr; ptr++) {
|
||||
if (*ptr == '\\') {
|
||||
*ptr = '/';
|
||||
}
|
||||
}
|
||||
|
||||
// drop the .exe name.
|
||||
ptr = SDL_strrchr(fullpath, '/');
|
||||
if (ptr) {
|
||||
ptr[1] = '\0';
|
||||
}
|
||||
|
||||
return fullpath;
|
||||
}
|
||||
|
||||
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
|
||||
{
|
||||
char *result = NULL;
|
||||
size_t len;
|
||||
if (!app) {
|
||||
SDL_InvalidParamError("app");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *base = SDL_GetBasePath();
|
||||
if (!base) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!org) {
|
||||
org = "";
|
||||
}
|
||||
|
||||
len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4;
|
||||
result = (char *)SDL_malloc(len);
|
||||
if (result) {
|
||||
if (*org) {
|
||||
SDL_snprintf(result, len, "%s%s", base, org);
|
||||
mkdir(result, 0755);
|
||||
SDL_snprintf(result, len, "%s%s/%s/", base, org, app);
|
||||
} else {
|
||||
SDL_snprintf(result, len, "%s%s/", base, app);
|
||||
}
|
||||
|
||||
mkdir(result, 0755);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *SDL_SYS_GetUserFolder(SDL_Folder folder)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif // SDL_FILESYSTEM_DOS
|
||||
@@ -106,6 +106,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = {
|
||||
#ifdef SDL_JOYSTICK_N3DS
|
||||
&SDL_N3DS_JoystickDriver,
|
||||
#endif
|
||||
#ifdef SDL_JOYSTICK_DOS
|
||||
&SDL_DOS_JoystickDriver,
|
||||
#endif
|
||||
#if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED)
|
||||
&SDL_DUMMY_JoystickDriver
|
||||
#endif
|
||||
|
||||
@@ -263,6 +263,7 @@ extern SDL_JoystickDriver SDL_PSP_JoystickDriver;
|
||||
extern SDL_JoystickDriver SDL_VITA_JoystickDriver;
|
||||
extern SDL_JoystickDriver SDL_N3DS_JoystickDriver;
|
||||
extern SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver;
|
||||
extern SDL_JoystickDriver SDL_DOS_JoystickDriver;
|
||||
|
||||
// Ends C function definitions when using C++
|
||||
#ifdef __cplusplus
|
||||
|
||||
342
src/joystick/dos/SDL_sysjoystick.c
Normal file
342
src/joystick/dos/SDL_sysjoystick.c
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_DOS
|
||||
|
||||
#include <dpmi.h> /* for __dpmi_regs, __dpmi_int */
|
||||
#include <limits.h>
|
||||
#include <pc.h> /* for inportb */
|
||||
|
||||
#include "../SDL_joystick_c.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
|
||||
#define GAMEPORT 0x201
|
||||
|
||||
/* Gameport status byte button bits (active low) */
|
||||
#define GAMEPORT_BUTTON1 0x10 /* bit 4 */
|
||||
#define GAMEPORT_BUTTON2 0x20 /* bit 5 */
|
||||
#define GAMEPORT_BUTTON3 0x40 /* bit 6 */
|
||||
#define GAMEPORT_BUTTON4 0x80 /* bit 7 */
|
||||
|
||||
/* Static state for detection */
|
||||
static bool dos_joystick_detected = false;
|
||||
static SDL_JoystickID dos_joystick_id = 0;
|
||||
static SDL_JoystickID dos_next_instance_id = 1;
|
||||
static Uint64 dos_joystick_next_poll_ns = 0;
|
||||
#define DOS_JOYSTICK_POLL_INTERVAL_NS SDL_MS_TO_NS(16) /* ~60 Hz is plenty for a 2-axis gameport stick */
|
||||
|
||||
struct joystick_hwdata
|
||||
{
|
||||
int axis_min[2]; /* minimum raw axis values seen */
|
||||
int axis_max[2]; /* maximum raw axis values seen */
|
||||
int axis_center[2]; /* center raw axis values (captured on first read) */
|
||||
bool calibrated; /* whether we've seen enough range */
|
||||
};
|
||||
|
||||
/*
|
||||
* Probe for joystick presence using BIOS INT 15h, function 84h, subfunction 0.
|
||||
* This reads the button state — if the BIOS supports it, a joystick is present.
|
||||
* Returns true if the BIOS call succeeds (carry flag clear).
|
||||
*/
|
||||
static bool ProbeGameport(void)
|
||||
{
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x8400; /* INT 15h AH=84h */
|
||||
regs.x.dx = 0x0000; /* subfunction 0: read button state */
|
||||
__dpmi_int(0x15, ®s);
|
||||
/* Carry flag set = no joystick BIOS support */
|
||||
return !(regs.x.flags & 0x01);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read joystick axes using BIOS INT 15h, function 84h, subfunction 1.
|
||||
* Returns calibrated raw values in AX (X axis) and BX (Y axis).
|
||||
* This avoids direct port I/O timing loops — the BIOS handles the
|
||||
* one-shot timer polling internally.
|
||||
*/
|
||||
static void ReadGameportAxes(int *axis_x, int *axis_y)
|
||||
{
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x8400; /* INT 15h AH=84h */
|
||||
regs.x.dx = 0x0001; /* subfunction 1: read axis values */
|
||||
__dpmi_int(0x15, ®s);
|
||||
if (regs.x.flags & 0x01) {
|
||||
/* BIOS call failed */
|
||||
*axis_x = -1;
|
||||
*axis_y = -1;
|
||||
} else {
|
||||
*axis_x = (int)regs.x.ax; /* joystick 1 X axis */
|
||||
*axis_y = (int)regs.x.bx; /* joystick 1 Y axis */
|
||||
}
|
||||
}
|
||||
|
||||
static Sint16 CalibrateAxis(int raw, struct joystick_hwdata *hwdata, int axis)
|
||||
{
|
||||
int range;
|
||||
int centered;
|
||||
|
||||
if (raw < 0) {
|
||||
return 0; /* axis not connected, report center */
|
||||
}
|
||||
|
||||
if (raw < hwdata->axis_min[axis]) {
|
||||
hwdata->axis_min[axis] = raw;
|
||||
}
|
||||
if (raw > hwdata->axis_max[axis]) {
|
||||
hwdata->axis_max[axis] = raw;
|
||||
}
|
||||
|
||||
if (!hwdata->calibrated) {
|
||||
hwdata->axis_center[axis] = raw;
|
||||
/* Consider calibrated once we've seen some range on either axis */
|
||||
if ((hwdata->axis_max[0] - hwdata->axis_min[0]) > 20 ||
|
||||
(hwdata->axis_max[1] - hwdata->axis_min[1]) > 20) {
|
||||
hwdata->calibrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
range = hwdata->axis_max[axis] - hwdata->axis_min[axis];
|
||||
if (range < 10) {
|
||||
range = 10; /* avoid division issues */
|
||||
}
|
||||
|
||||
/* Map to -32768..32767 */
|
||||
centered = raw - hwdata->axis_center[axis];
|
||||
return (Sint16)SDL_clamp(((Sint64)centered * 65535) / range, -32768, 32767);
|
||||
}
|
||||
|
||||
static bool DOS_JoystickInit(void)
|
||||
{
|
||||
dos_joystick_detected = ProbeGameport();
|
||||
if (dos_joystick_detected) {
|
||||
dos_joystick_id = dos_next_instance_id++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int DOS_JoystickGetCount(void)
|
||||
{
|
||||
return dos_joystick_detected ? 1 : 0;
|
||||
}
|
||||
|
||||
static void DOS_JoystickDetect(void)
|
||||
{
|
||||
/* Don't re-probe every frame — ProbeGameport() does a tight loop of up to
|
||||
65536 port reads, which is very expensive and can interfere with SB16 IRQ
|
||||
timing. DOS gameport joysticks are not hot-pluggable anyway. Detection
|
||||
happens once in DOS_JoystickInit(). */
|
||||
}
|
||||
|
||||
static bool DOS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *DOS_JoystickGetDeviceName(int device_index)
|
||||
{
|
||||
if (device_index == 0 && dos_joystick_detected) {
|
||||
return "DOS Gameport Joystick";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *DOS_JoystickGetDevicePath(int device_index)
|
||||
{
|
||||
if (device_index == 0 && dos_joystick_detected) {
|
||||
return "gameport:0x201";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int DOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int DOS_JoystickGetDevicePlayerIndex(int device_index)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void DOS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static SDL_GUID DOS_JoystickGetDeviceGUID(int device_index)
|
||||
{
|
||||
return SDL_CreateJoystickGUID(
|
||||
SDL_HARDWARE_BUS_UNKNOWN, /* bus */
|
||||
0x0000, /* vendor */
|
||||
0x0201, /* product (port number) */
|
||||
0x0001, /* version */
|
||||
NULL, /* vendor_name */
|
||||
"DOS Gameport Joystick", /* product_name */
|
||||
0, /* driver_signature */
|
||||
0 /* driver_data */
|
||||
);
|
||||
}
|
||||
|
||||
static SDL_JoystickID DOS_JoystickGetDeviceInstanceID(int device_index)
|
||||
{
|
||||
if (device_index == 0 && dos_joystick_detected) {
|
||||
return dos_joystick_id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool DOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
|
||||
{
|
||||
struct joystick_hwdata *hwdata;
|
||||
|
||||
hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata));
|
||||
if (!hwdata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hwdata->axis_min[0] = INT_MAX;
|
||||
hwdata->axis_min[1] = INT_MAX;
|
||||
hwdata->axis_max[0] = 0;
|
||||
hwdata->axis_max[1] = 0;
|
||||
hwdata->axis_center[0] = 0;
|
||||
hwdata->axis_center[1] = 0;
|
||||
hwdata->calibrated = false;
|
||||
|
||||
joystick->hwdata = hwdata;
|
||||
joystick->naxes = 2;
|
||||
joystick->nbuttons = 4;
|
||||
joystick->nhats = 0;
|
||||
joystick->nballs = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool DOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool DOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool DOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool DOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void DOS_JoystickUpdate(SDL_Joystick *joystick)
|
||||
{
|
||||
struct joystick_hwdata *hwdata = joystick->hwdata;
|
||||
int axis_x, axis_y;
|
||||
Uint8 val;
|
||||
Uint64 now;
|
||||
|
||||
if (!hwdata) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Buttons are a passive port read (no timing loop), always safe to poll */
|
||||
val = inportb(GAMEPORT);
|
||||
SDL_SendJoystickButton(0, joystick, 0, !(val & GAMEPORT_BUTTON1));
|
||||
SDL_SendJoystickButton(0, joystick, 1, !(val & GAMEPORT_BUTTON2));
|
||||
SDL_SendJoystickButton(0, joystick, 2, !(val & GAMEPORT_BUTTON3));
|
||||
SDL_SendJoystickButton(0, joystick, 3, !(val & GAMEPORT_BUTTON4));
|
||||
|
||||
/* Throttle axis reads — BIOS INT 15h subfunction 1 does an internal
|
||||
timing loop that is very expensive. ~60 Hz is more than enough for
|
||||
a 2-axis analog gameport stick. */
|
||||
now = SDL_GetTicksNS();
|
||||
if (now < dos_joystick_next_poll_ns) {
|
||||
return;
|
||||
}
|
||||
dos_joystick_next_poll_ns = now + DOS_JOYSTICK_POLL_INTERVAL_NS;
|
||||
|
||||
ReadGameportAxes(&axis_x, &axis_y);
|
||||
|
||||
if (axis_x >= 0) {
|
||||
Sint16 cal_x = CalibrateAxis(axis_x, hwdata, 0);
|
||||
SDL_SendJoystickAxis(0, joystick, 0, cal_x);
|
||||
}
|
||||
|
||||
if (axis_y >= 0) {
|
||||
Sint16 cal_y = CalibrateAxis(axis_y, hwdata, 1);
|
||||
SDL_SendJoystickAxis(0, joystick, 1, cal_y);
|
||||
}
|
||||
}
|
||||
|
||||
static void DOS_JoystickClose(SDL_Joystick *joystick)
|
||||
{
|
||||
if (joystick->hwdata) {
|
||||
SDL_free(joystick->hwdata);
|
||||
joystick->hwdata = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void DOS_JoystickQuit(void)
|
||||
{
|
||||
dos_joystick_detected = false;
|
||||
dos_joystick_id = 0;
|
||||
}
|
||||
|
||||
static bool DOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_JoystickDriver SDL_DOS_JoystickDriver = {
|
||||
DOS_JoystickInit,
|
||||
DOS_JoystickGetCount,
|
||||
DOS_JoystickDetect,
|
||||
DOS_JoystickIsDevicePresent,
|
||||
DOS_JoystickGetDeviceName,
|
||||
DOS_JoystickGetDevicePath,
|
||||
DOS_JoystickGetDeviceSteamVirtualGamepadSlot,
|
||||
DOS_JoystickGetDevicePlayerIndex,
|
||||
DOS_JoystickSetDevicePlayerIndex,
|
||||
DOS_JoystickGetDeviceGUID,
|
||||
DOS_JoystickGetDeviceInstanceID,
|
||||
DOS_JoystickOpen,
|
||||
DOS_JoystickRumble,
|
||||
DOS_JoystickRumbleTriggers,
|
||||
DOS_JoystickSetLED,
|
||||
DOS_JoystickSendEffect,
|
||||
DOS_JoystickSetSensorsEnabled,
|
||||
DOS_JoystickUpdate,
|
||||
DOS_JoystickClose,
|
||||
DOS_JoystickQuit,
|
||||
DOS_JoystickGetGamepadMapping
|
||||
};
|
||||
|
||||
#endif /* SDL_JOYSTICK_DOS */
|
||||
@@ -29,7 +29,8 @@
|
||||
!defined(SDL_PLATFORM_EMSCRIPTEN) && \
|
||||
!defined(SDL_PLATFORM_PSP) && \
|
||||
!defined(SDL_PLATFORM_PS2) && \
|
||||
!defined(SDL_PLATFORM_3DS)
|
||||
!defined(SDL_PLATFORM_3DS) && \
|
||||
!defined(SDL_PLATFORM_DOS)
|
||||
|
||||
int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved)
|
||||
{
|
||||
|
||||
51
src/main/dos/SDL_sysmain_runapp.c
Normal file
51
src/main/dos/SDL_sysmain_runapp.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../SDL_main_callbacks.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
|
||||
#include <sys/nearptr.h>
|
||||
|
||||
// this locks .data, .bss, .text, and the stack. In SDL_RunApp(), we'll adjust this flag so future malloc() calls aren't locked by default.
|
||||
#include <crt0.h>
|
||||
int _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY | _CRT0_FLAG_NONMOVE_SBRK;
|
||||
|
||||
const char *SDL_argv0 = NULL;
|
||||
|
||||
int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved)
|
||||
{
|
||||
(void)reserved;
|
||||
_crt0_startup_flags &= ~_CRT0_FLAG_LOCK_MEMORY; // don't lock further allocations by default...so data, code, and stack are locked but not buffers from future malloc() calls.
|
||||
|
||||
if (!__djgpp_nearptr_enable()) {
|
||||
fprintf(stderr, "__djgpp_nearptr_enable() failed!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_argv0 = argv ? argv[0] : NULL;
|
||||
|
||||
return SDL_CallMainFunction(argc, argv, mainFunction);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -36,7 +36,7 @@
|
||||
#define SDL_SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__
|
||||
#elif defined(SDL_PLATFORM_NGAGE)
|
||||
#define SDL_SIZEOF_WCHAR_T 2
|
||||
#elif defined(SDL_PLATFORM_WINDOWS)
|
||||
#elif defined(SDL_PLATFORM_WINDOWS) || defined(SDL_PLATFORM_DOS)
|
||||
#define SDL_SIZEOF_WCHAR_T 2
|
||||
#else // assume everything else is UTF-32 (add more tests if compiler-assert fails below!)
|
||||
#define SDL_SIZEOF_WCHAR_T 4
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include "vita/SDL_systhread_c.h"
|
||||
#elif defined(SDL_THREAD_N3DS)
|
||||
#include "n3ds/SDL_systhread_c.h"
|
||||
#elif defined(SDL_THREAD_DOS)
|
||||
#include "dos/SDL_systhread_c.h"
|
||||
#else
|
||||
#error Need thread implementation for this platform
|
||||
#include "generic/SDL_systhread_c.h"
|
||||
|
||||
122
src/thread/dos/SDL_sysmutex.c
Normal file
122
src/thread/dos/SDL_sysmutex.c
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_THREAD_DOS
|
||||
|
||||
/* Mutex implementation for DOS cooperative threading.
|
||||
Uses cli/sti for atomicity and cooperative yielding for contention. */
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
|
||||
#define MUTEX_NO_OWNER -1
|
||||
|
||||
struct SDL_Mutex
|
||||
{
|
||||
volatile int owner; /* Thread ID of owner, or MUTEX_NO_OWNER if unlocked */
|
||||
volatile int recursive; /* Recursion count */
|
||||
};
|
||||
|
||||
SDL_Mutex *SDL_CreateMutex(void)
|
||||
{
|
||||
SDL_Mutex *mutex = (SDL_Mutex *)SDL_malloc(sizeof(*mutex));
|
||||
if (mutex) {
|
||||
mutex->owner = MUTEX_NO_OWNER;
|
||||
mutex->recursive = 0;
|
||||
}
|
||||
return mutex;
|
||||
}
|
||||
|
||||
void SDL_DestroyMutex(SDL_Mutex *mutex)
|
||||
{
|
||||
if (mutex) {
|
||||
SDL_free(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_LockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
if (!mutex) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tid = DOS_GetCurrentThreadID();
|
||||
|
||||
for (;;) {
|
||||
DOS_DisableInterrupts();
|
||||
if (mutex->owner == MUTEX_NO_OWNER) {
|
||||
mutex->owner = tid;
|
||||
mutex->recursive = 1;
|
||||
DOS_EnableInterrupts();
|
||||
return;
|
||||
}
|
||||
if (mutex->owner == tid) {
|
||||
mutex->recursive++;
|
||||
DOS_EnableInterrupts();
|
||||
return;
|
||||
}
|
||||
DOS_EnableInterrupts();
|
||||
DOS_Yield();
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_TryLockMutex(SDL_Mutex *mutex)
|
||||
{
|
||||
if (!mutex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int tid = DOS_GetCurrentThreadID();
|
||||
|
||||
DOS_DisableInterrupts();
|
||||
if (mutex->owner == MUTEX_NO_OWNER) {
|
||||
mutex->owner = tid;
|
||||
mutex->recursive = 1;
|
||||
DOS_EnableInterrupts();
|
||||
return true;
|
||||
}
|
||||
if (mutex->owner == tid) {
|
||||
mutex->recursive++;
|
||||
DOS_EnableInterrupts();
|
||||
return true;
|
||||
}
|
||||
DOS_EnableInterrupts();
|
||||
return false;
|
||||
}
|
||||
|
||||
void SDL_UnlockMutex(SDL_Mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
if (!mutex) {
|
||||
return;
|
||||
}
|
||||
|
||||
DOS_DisableInterrupts();
|
||||
if (mutex->recursive > 1) {
|
||||
mutex->recursive--;
|
||||
} else {
|
||||
mutex->owner = MUTEX_NO_OWNER;
|
||||
mutex->recursive = 0;
|
||||
}
|
||||
DOS_EnableInterrupts();
|
||||
}
|
||||
|
||||
#endif /* SDL_THREAD_DOS */
|
||||
115
src/thread/dos/SDL_syssem.c
Normal file
115
src/thread/dos/SDL_syssem.c
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_THREAD_DOS
|
||||
|
||||
/* Semaphore implementation for DOS cooperative threading.
|
||||
Uses cli/sti for atomicity and cooperative yielding for waits. */
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
|
||||
struct SDL_Semaphore
|
||||
{
|
||||
volatile Uint32 count;
|
||||
};
|
||||
|
||||
SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value)
|
||||
{
|
||||
SDL_Semaphore *sem = (SDL_Semaphore *)SDL_malloc(sizeof(*sem));
|
||||
if (sem) {
|
||||
sem->count = initial_value;
|
||||
}
|
||||
return sem;
|
||||
}
|
||||
|
||||
void SDL_DestroySemaphore(SDL_Semaphore *sem)
|
||||
{
|
||||
if (sem) {
|
||||
SDL_free(sem);
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS)
|
||||
{
|
||||
if (!sem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Try-wait (poll): check and decrement if possible */
|
||||
if (timeoutNS == 0) {
|
||||
bool acquired = false;
|
||||
DOS_DisableInterrupts();
|
||||
if (sem->count > 0) {
|
||||
sem->count--;
|
||||
acquired = true;
|
||||
}
|
||||
DOS_EnableInterrupts();
|
||||
return acquired;
|
||||
}
|
||||
|
||||
/* Indefinite wait (-1) or timed wait */
|
||||
Uint64 deadline = 0;
|
||||
if (timeoutNS > 0) {
|
||||
deadline = SDL_GetPerformanceCounter() +
|
||||
(Uint64)((double)timeoutNS * (double)SDL_GetPerformanceFrequency() / 1e9);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
DOS_DisableInterrupts();
|
||||
if (sem->count > 0) {
|
||||
sem->count--;
|
||||
DOS_EnableInterrupts();
|
||||
return true; /* Acquired */
|
||||
}
|
||||
DOS_EnableInterrupts();
|
||||
|
||||
/* Check timeout */
|
||||
if (timeoutNS > 0 && SDL_GetPerformanceCounter() >= deadline) {
|
||||
return false; /* Timed out */
|
||||
}
|
||||
|
||||
/* Yield to other threads instead of busy-spinning */
|
||||
DOS_Yield();
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem)
|
||||
{
|
||||
if (!sem) {
|
||||
return 0;
|
||||
}
|
||||
return sem->count;
|
||||
}
|
||||
|
||||
void SDL_SignalSemaphore(SDL_Semaphore *sem)
|
||||
{
|
||||
if (!sem) {
|
||||
return;
|
||||
}
|
||||
|
||||
DOS_DisableInterrupts();
|
||||
sem->count++;
|
||||
DOS_EnableInterrupts();
|
||||
}
|
||||
|
||||
#endif /* SDL_THREAD_DOS */
|
||||
87
src/thread/dos/SDL_systhread.c
Normal file
87
src/thread/dos/SDL_systhread.c
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_THREAD_DOS
|
||||
|
||||
/* DOS thread management routines for SDL — cooperative threading via
|
||||
the DOS mini-scheduler (no OS-level threads on DOS). */
|
||||
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
#include "../SDL_systhread.h"
|
||||
#include "../SDL_thread_c.h"
|
||||
|
||||
static int ThreadEntry(void *arg)
|
||||
{
|
||||
SDL_Thread *thread = (SDL_Thread *)arg;
|
||||
SDL_RunThread(thread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SDL_SYS_CreateThread(SDL_Thread *thread,
|
||||
SDL_FunctionPointer pfnBeginThread,
|
||||
SDL_FunctionPointer pfnEndThread)
|
||||
{
|
||||
/* Ensure the scheduler is initialized (idempotent) */
|
||||
DOS_SchedulerInit();
|
||||
|
||||
size_t stack_size = thread->stacksize;
|
||||
|
||||
int tid = DOS_CreateThread(ThreadEntry, thread, stack_size);
|
||||
if (tid < 0) {
|
||||
return SDL_SetError("DOS_CreateThread() failed — no free thread slots");
|
||||
}
|
||||
|
||||
thread->handle = tid;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_SYS_SetupThread(const char *name)
|
||||
{
|
||||
/* Nothing to do on DOS */
|
||||
}
|
||||
|
||||
SDL_ThreadID SDL_GetCurrentThreadID(void)
|
||||
{
|
||||
return (SDL_ThreadID)DOS_GetCurrentThreadID();
|
||||
}
|
||||
|
||||
void SDL_SYS_WaitThread(SDL_Thread *thread)
|
||||
{
|
||||
DOS_JoinThread(thread->handle);
|
||||
DOS_DestroyThread(thread->handle);
|
||||
}
|
||||
|
||||
void SDL_SYS_DetachThread(SDL_Thread *thread)
|
||||
{
|
||||
/* For cooperative threads, detach is a no-op. The thread will clean
|
||||
itself up when it finishes. In practice, SDL's thread code handles
|
||||
the detach lifecycle via atomics in SDL_RunThread. */
|
||||
}
|
||||
|
||||
bool SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
|
||||
{
|
||||
/* DOS cooperative scheduler uses round-robin — priority is not
|
||||
meaningful. Accept any value without error. */
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* SDL_THREAD_DOS */
|
||||
28
src/thread/dos/SDL_systhread_c.h
Normal file
28
src/thread/dos/SDL_systhread_c.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_systhread_c_h_
|
||||
#define SDL_systhread_c_h_
|
||||
|
||||
/* DOS thread handle is an integer thread ID from the DOS scheduler */
|
||||
typedef int SYS_ThreadHandle;
|
||||
|
||||
#endif /* SDL_systhread_c_h_ */
|
||||
63
src/thread/dos/SDL_systls.c
Normal file
63
src/thread/dos/SDL_systls.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_THREAD_DOS
|
||||
|
||||
/* DOS thread-local storage — uses per-thread static array indexed by
|
||||
the scheduler's thread ID. With only DOS_MAX_THREADS (8) threads,
|
||||
a simple static array is efficient and avoids any dynamic allocation. */
|
||||
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
#include "../SDL_thread_c.h"
|
||||
|
||||
static SDL_TLSData *tls_data[DOS_MAX_THREADS];
|
||||
|
||||
void SDL_SYS_InitTLSData(void)
|
||||
{
|
||||
SDL_memset(tls_data, 0, sizeof(tls_data));
|
||||
}
|
||||
|
||||
SDL_TLSData *SDL_SYS_GetTLSData(void)
|
||||
{
|
||||
int tid = DOS_GetCurrentThreadID();
|
||||
if (tid < 0 || tid >= DOS_MAX_THREADS) {
|
||||
return NULL;
|
||||
}
|
||||
return tls_data[tid];
|
||||
}
|
||||
|
||||
bool SDL_SYS_SetTLSData(SDL_TLSData *data)
|
||||
{
|
||||
int tid = DOS_GetCurrentThreadID();
|
||||
if (tid < 0 || tid >= DOS_MAX_THREADS) {
|
||||
return SDL_SetError("Invalid thread ID for TLS");
|
||||
}
|
||||
tls_data[tid] = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_SYS_QuitTLSData(void)
|
||||
{
|
||||
SDL_memset(tls_data, 0, sizeof(tls_data));
|
||||
}
|
||||
|
||||
#endif /* SDL_THREAD_DOS */
|
||||
@@ -24,7 +24,9 @@
|
||||
|
||||
#include "../SDL_time_c.h"
|
||||
#include <errno.h>
|
||||
#ifndef SDL_PLATFORM_DOS
|
||||
#include <langinfo.h>
|
||||
#endif
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
70
src/timer/dos/SDL_systimer.c
Normal file
70
src/timer/dos/SDL_systimer.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_TIMER_DOS
|
||||
|
||||
#include <dos.h> /* delay */
|
||||
#include <time.h> /* uclock, uclock_t, UCLOCKS_PER_SEC */
|
||||
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
|
||||
/* DJGPP's uclock() reprograms PIT channel 0 for a higher tick rate on first
|
||||
call, giving ~1.19 MHz resolution (UCLOCKS_PER_SEC == 1193180). This is
|
||||
the same approach SDL2-dos used and gives sub-microsecond precision without
|
||||
any extra setup. */
|
||||
|
||||
Uint64 SDL_GetPerformanceCounter(void)
|
||||
{
|
||||
return (Uint64)uclock();
|
||||
}
|
||||
|
||||
Uint64 SDL_GetPerformanceFrequency(void)
|
||||
{
|
||||
return (Uint64)UCLOCKS_PER_SEC;
|
||||
}
|
||||
|
||||
void SDL_SYS_DelayNS(Uint64 ns)
|
||||
{
|
||||
if (ns == 0) {
|
||||
DOS_Yield();
|
||||
return;
|
||||
}
|
||||
|
||||
const uclock_t delay_start = uclock();
|
||||
const uclock_t target_ticks = (uclock_t)((ns * UCLOCKS_PER_SEC) / SDL_NS_PER_SECOND);
|
||||
|
||||
while ((uclock() - delay_start) < target_ticks) {
|
||||
/* Always yield first so cooperative threads can run. */
|
||||
DOS_Yield();
|
||||
|
||||
/* If more than 1 ms remains, do a short sleep to avoid burning
|
||||
100% CPU when no other threads need to run. DJGPP's delay()
|
||||
is a busy-wait but it does halt-loop on the PIT, which is
|
||||
lighter than a tight uclock() poll. */
|
||||
uclock_t remaining = target_ticks - (uclock() - delay_start);
|
||||
if (remaining > (UCLOCKS_PER_SEC / 1000)) {
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SDL_TIMER_DOS */
|
||||
@@ -546,6 +546,7 @@ extern VideoBootStrap Emscripten_bootstrap;
|
||||
extern VideoBootStrap OFFSCREEN_bootstrap;
|
||||
extern VideoBootStrap QNX_bootstrap;
|
||||
extern VideoBootStrap OPENVR_bootstrap;
|
||||
extern VideoBootStrap DOSVESA_bootstrap;
|
||||
|
||||
extern bool SDL_UninitializedVideo(void);
|
||||
// Use SDL_OnVideoThread() sparingly, to avoid regressions in use cases that currently happen to work
|
||||
|
||||
@@ -149,6 +149,9 @@ static VideoBootStrap *bootstrap[] = {
|
||||
&DUMMY_evdev_bootstrap,
|
||||
#endif
|
||||
#endif
|
||||
#ifdef SDL_VIDEO_DRIVER_DOSVESA
|
||||
&DOSVESA_bootstrap,
|
||||
#endif
|
||||
#ifdef SDL_VIDEO_DRIVER_OPENVR
|
||||
&OPENVR_bootstrap,
|
||||
#endif
|
||||
|
||||
397
src/video/dos/SDL_dosevents.c
Normal file
397
src/video/dos/SDL_dosevents.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_DOSVESA
|
||||
|
||||
#include "../../events/SDL_events_c.h"
|
||||
#include "../../events/SDL_keyboard_c.h"
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
#include "../SDL_sysvideo.h"
|
||||
#include "SDL_dosvideo.h"
|
||||
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
#include "SDL_dosevents_c.h"
|
||||
|
||||
// PS/2 keyboard controller port
|
||||
#define KBD_DATA_PORT 0x60
|
||||
|
||||
// Scancode byte structure
|
||||
#define SCANCODE_MASK 0x7F // bits 0-6: scancode index
|
||||
#define SCANCODE_RELEASE 0x80 // bit 7: key released (break code)
|
||||
|
||||
// Multi-byte scancode prefixes
|
||||
#define SCANCODE_PREFIX_EXTENDED 0xE0 // extended key prefix
|
||||
#define SCANCODE_PREFIX_PAUSE 0xE1 // pause key sequence prefix
|
||||
|
||||
// VGA Input Status Register 1 (for vblank detection)
|
||||
#define VGA_STATUS_PORT 0x3DA
|
||||
#define VGA_STATUS_VBLANK 0x08 // bit 3: vertical retrace active
|
||||
|
||||
// Scancode table: https://www.plantation-productions.com/Webster/www.artofasm.com/DOS/pdf/apndxc.pdf
|
||||
static const SDL_Scancode DOSVESA_ScancodeMapping[] = { // index is the scancode from the IRQ1 handler bitwise-ANDed against 0x7F.
|
||||
/* 0x00 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x01 */ SDL_SCANCODE_ESCAPE,
|
||||
/* 0x02 */ SDL_SCANCODE_1,
|
||||
/* 0x03 */ SDL_SCANCODE_2,
|
||||
/* 0x04 */ SDL_SCANCODE_3,
|
||||
/* 0x05 */ SDL_SCANCODE_4,
|
||||
/* 0x06 */ SDL_SCANCODE_5,
|
||||
/* 0x07 */ SDL_SCANCODE_6,
|
||||
/* 0x08 */ SDL_SCANCODE_7,
|
||||
/* 0x09 */ SDL_SCANCODE_8,
|
||||
/* 0x0A */ SDL_SCANCODE_9,
|
||||
/* 0x0B */ SDL_SCANCODE_0,
|
||||
/* 0x0C */ SDL_SCANCODE_MINUS,
|
||||
/* 0x0D */ SDL_SCANCODE_EQUALS,
|
||||
/* 0x0E */ SDL_SCANCODE_BACKSPACE,
|
||||
/* 0x0F */ SDL_SCANCODE_TAB,
|
||||
|
||||
/* 0x10 */ SDL_SCANCODE_Q,
|
||||
/* 0x11 */ SDL_SCANCODE_W,
|
||||
/* 0x12 */ SDL_SCANCODE_E,
|
||||
/* 0x13 */ SDL_SCANCODE_R,
|
||||
/* 0x14 */ SDL_SCANCODE_T,
|
||||
/* 0x15 */ SDL_SCANCODE_Y,
|
||||
/* 0x16 */ SDL_SCANCODE_U,
|
||||
/* 0x17 */ SDL_SCANCODE_I,
|
||||
/* 0x18 */ SDL_SCANCODE_O,
|
||||
/* 0x19 */ SDL_SCANCODE_P,
|
||||
/* 0x1A */ SDL_SCANCODE_LEFTBRACKET,
|
||||
/* 0x1B */ SDL_SCANCODE_RIGHTBRACKET,
|
||||
/* 0x1C */ SDL_SCANCODE_RETURN,
|
||||
/* 0x1D */ SDL_SCANCODE_LCTRL,
|
||||
/* 0x1E */ SDL_SCANCODE_A,
|
||||
/* 0x1F */ SDL_SCANCODE_S,
|
||||
|
||||
/* 0x20 */ SDL_SCANCODE_D,
|
||||
/* 0x21 */ SDL_SCANCODE_F,
|
||||
/* 0x22 */ SDL_SCANCODE_G,
|
||||
/* 0x23 */ SDL_SCANCODE_H,
|
||||
/* 0x24 */ SDL_SCANCODE_J,
|
||||
/* 0x25 */ SDL_SCANCODE_K,
|
||||
/* 0x26 */ SDL_SCANCODE_L,
|
||||
/* 0x27 */ SDL_SCANCODE_SEMICOLON,
|
||||
/* 0x28 */ SDL_SCANCODE_APOSTROPHE,
|
||||
/* 0x29 */ SDL_SCANCODE_GRAVE,
|
||||
/* 0x2A */ SDL_SCANCODE_LSHIFT,
|
||||
/* 0x2B */ SDL_SCANCODE_BACKSLASH,
|
||||
/* 0x2C */ SDL_SCANCODE_Z,
|
||||
/* 0x2D */ SDL_SCANCODE_X,
|
||||
/* 0x2E */ SDL_SCANCODE_C,
|
||||
/* 0x2F */ SDL_SCANCODE_V,
|
||||
|
||||
/* 0x30 */ SDL_SCANCODE_B,
|
||||
/* 0x31 */ SDL_SCANCODE_N,
|
||||
/* 0x32 */ SDL_SCANCODE_M,
|
||||
/* 0x33 */ SDL_SCANCODE_COMMA,
|
||||
/* 0x34 */ SDL_SCANCODE_PERIOD,
|
||||
/* 0x35 */ SDL_SCANCODE_SLASH,
|
||||
/* 0x36 */ SDL_SCANCODE_RSHIFT,
|
||||
/* 0x37 */ SDL_SCANCODE_KP_MULTIPLY,
|
||||
/* 0x38 */ SDL_SCANCODE_LALT,
|
||||
/* 0x39 */ SDL_SCANCODE_SPACE,
|
||||
/* 0x3A */ SDL_SCANCODE_CAPSLOCK,
|
||||
/* 0x3B */ SDL_SCANCODE_F1,
|
||||
/* 0x3C */ SDL_SCANCODE_F2,
|
||||
/* 0x3D */ SDL_SCANCODE_F3,
|
||||
/* 0x3E */ SDL_SCANCODE_F4,
|
||||
/* 0x3F */ SDL_SCANCODE_F5,
|
||||
|
||||
/* 0x040 */ SDL_SCANCODE_F6,
|
||||
/* 0x041 */ SDL_SCANCODE_F7,
|
||||
/* 0x042 */ SDL_SCANCODE_F8,
|
||||
/* 0x043 */ SDL_SCANCODE_F9,
|
||||
/* 0x044 */ SDL_SCANCODE_F10,
|
||||
/* 0x045 */ SDL_SCANCODE_NUMLOCKCLEAR,
|
||||
/* 0x046 */ SDL_SCANCODE_SCROLLLOCK,
|
||||
/* 0x047 */ SDL_SCANCODE_KP_7,
|
||||
/* 0x048 */ SDL_SCANCODE_KP_8,
|
||||
/* 0x049 */ SDL_SCANCODE_KP_9,
|
||||
/* 0x04A */ SDL_SCANCODE_KP_MINUS,
|
||||
/* 0x04B */ SDL_SCANCODE_KP_4,
|
||||
/* 0x04C */ SDL_SCANCODE_KP_5,
|
||||
/* 0x04D */ SDL_SCANCODE_KP_6,
|
||||
/* 0x04E */ SDL_SCANCODE_KP_PLUS,
|
||||
/* 0x04F */ SDL_SCANCODE_KP_1,
|
||||
|
||||
/* 0x050 */ SDL_SCANCODE_KP_2,
|
||||
/* 0x051 */ SDL_SCANCODE_KP_3,
|
||||
/* 0x052 */ SDL_SCANCODE_KP_0,
|
||||
/* 0x053 */ SDL_SCANCODE_KP_PERIOD,
|
||||
/* 0x054 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x055 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x056 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x057 */ SDL_SCANCODE_F11,
|
||||
/* 0x058 */ SDL_SCANCODE_F12
|
||||
};
|
||||
|
||||
// Extended scancode table for keys prefixed with 0xE0
|
||||
static const SDL_Scancode DOSVESA_ExtendedScancodeMapping[] = { // index is the scancode byte following the 0xE0 prefix, masked with 0x7F.
|
||||
/* 0x00 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x01 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x02 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x03 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x04 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x05 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x06 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x07 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x08 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x09 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x0A */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x0B */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x0C */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x0D */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x0E */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x0F */ SDL_SCANCODE_UNKNOWN,
|
||||
|
||||
/* 0x10 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x11 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x12 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x13 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x14 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x15 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x16 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x17 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x18 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x19 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x1A */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x1B */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x1C */ SDL_SCANCODE_KP_ENTER,
|
||||
/* 0x1D */ SDL_SCANCODE_RCTRL,
|
||||
/* 0x1E */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x1F */ SDL_SCANCODE_UNKNOWN,
|
||||
|
||||
/* 0x20 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x21 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x22 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x23 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x24 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x25 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x26 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x27 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x28 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x29 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x2A */ SDL_SCANCODE_UNKNOWN, // fake left shift, ignore
|
||||
/* 0x2B */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x2C */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x2D */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x2E */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x2F */ SDL_SCANCODE_UNKNOWN,
|
||||
|
||||
/* 0x30 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x31 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x32 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x33 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x34 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x35 */ SDL_SCANCODE_KP_DIVIDE,
|
||||
/* 0x36 */ SDL_SCANCODE_UNKNOWN, // fake right shift, ignore
|
||||
/* 0x37 */ SDL_SCANCODE_PRINTSCREEN,
|
||||
/* 0x38 */ SDL_SCANCODE_RALT,
|
||||
/* 0x39 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x3A */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x3B */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x3C */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x3D */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x3E */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x3F */ SDL_SCANCODE_UNKNOWN,
|
||||
|
||||
/* 0x40 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x41 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x42 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x43 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x44 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x45 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x46 */ SDL_SCANCODE_PAUSE, // Ctrl+Break sends E0 46 E0 C6
|
||||
/* 0x47 */ SDL_SCANCODE_HOME,
|
||||
/* 0x48 */ SDL_SCANCODE_UP,
|
||||
/* 0x49 */ SDL_SCANCODE_PAGEUP,
|
||||
/* 0x4A */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x4B */ SDL_SCANCODE_LEFT,
|
||||
/* 0x4C */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x4D */ SDL_SCANCODE_RIGHT,
|
||||
/* 0x4E */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x4F */ SDL_SCANCODE_END,
|
||||
|
||||
/* 0x50 */ SDL_SCANCODE_DOWN,
|
||||
/* 0x51 */ SDL_SCANCODE_PAGEDOWN,
|
||||
/* 0x52 */ SDL_SCANCODE_INSERT,
|
||||
/* 0x53 */ SDL_SCANCODE_DELETE,
|
||||
/* 0x54 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x55 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x56 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x57 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x58 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x59 */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x5A */ SDL_SCANCODE_UNKNOWN,
|
||||
/* 0x5B */ SDL_SCANCODE_LGUI,
|
||||
/* 0x5C */ SDL_SCANCODE_RGUI,
|
||||
/* 0x5D */ SDL_SCANCODE_APPLICATION
|
||||
};
|
||||
|
||||
static Uint8 keyevents_ringbuffer[256];
|
||||
static int keyevents_head = 0;
|
||||
static int keyevents_tail = 0;
|
||||
|
||||
void DOSVESA_PumpEvents(SDL_VideoDevice *device)
|
||||
{
|
||||
/* Give cooperative threads a chance to run. Audio mixing now runs
|
||||
in its own cooperative thread (via SDL's normal audio thread),
|
||||
so it will execute during this yield along with loading threads
|
||||
and anything else that is runnable. */
|
||||
DOS_Yield();
|
||||
|
||||
static bool is_extended = false;
|
||||
static int pause_sequence_remaining = 0;
|
||||
|
||||
while (keyevents_head != keyevents_tail) {
|
||||
const Uint8 event = keyevents_ringbuffer[keyevents_tail];
|
||||
keyevents_tail = (keyevents_tail + 1) & (SDL_arraysize(keyevents_ringbuffer) - 1);
|
||||
|
||||
// Handle remaining bytes of E1 Pause key sequence (E1 1D 45 E1 9D C5).
|
||||
if (pause_sequence_remaining > 0) {
|
||||
pause_sequence_remaining--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pause key sends a multi-byte sequence: E1 1D 45 E1 9D C5. Emit PAUSE press+release and consume the rest.
|
||||
if (event == SCANCODE_PREFIX_PAUSE) {
|
||||
pause_sequence_remaining = 5; // skip the next 5 bytes
|
||||
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_PAUSE, true);
|
||||
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_PAUSE, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event == SCANCODE_PREFIX_EXTENDED) {
|
||||
is_extended = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const int scancode = (int)(event & SCANCODE_MASK);
|
||||
const bool pressed = ((event & SCANCODE_RELEASE) == 0);
|
||||
|
||||
SDL_Scancode sc = SDL_SCANCODE_UNKNOWN;
|
||||
|
||||
if (is_extended) {
|
||||
is_extended = false;
|
||||
if (scancode < SDL_arraysize(DOSVESA_ExtendedScancodeMapping)) {
|
||||
sc = DOSVESA_ExtendedScancodeMapping[scancode];
|
||||
}
|
||||
} else {
|
||||
if (scancode < SDL_arraysize(DOSVESA_ScancodeMapping)) {
|
||||
sc = DOSVESA_ScancodeMapping[scancode];
|
||||
}
|
||||
}
|
||||
|
||||
if (sc != SDL_SCANCODE_UNKNOWN) {
|
||||
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, sc, pressed);
|
||||
|
||||
// Generate text input events for key-down on printable characters.
|
||||
// SDL keycodes below SDLK_SCANCODE_MASK are Unicode codepoints.
|
||||
if (pressed) {
|
||||
SDL_Keymod mod = SDL_GetModState();
|
||||
if (!(mod & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) {
|
||||
SDL_Keycode keycode = SDL_GetKeyFromScancode(sc, mod, false);
|
||||
if (keycode > 0 && keycode < SDLK_SCANCODE_MASK && !SDL_iscntrl((int)keycode)) {
|
||||
char text[5];
|
||||
char *end = SDL_UCS4ToUTF8((Uint32)keycode, text);
|
||||
*end = '\0';
|
||||
SDL_SendKeyboardText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
if (mouse->internal) { // if non-NULL, there's a mouse detected on the system.
|
||||
__dpmi_regs regs;
|
||||
|
||||
regs.x.ax = 0x3; // read mouse buttons and position.
|
||||
__dpmi_int(0x33, ®s);
|
||||
const Uint16 buttons = (int)(Sint16)regs.x.bx;
|
||||
|
||||
SDL_SendMouseButton(0, mouse->focus, SDL_DEFAULT_MOUSE_ID, SDL_BUTTON_LEFT, (buttons & (1 << 0)) != 0);
|
||||
SDL_SendMouseButton(0, mouse->focus, SDL_DEFAULT_MOUSE_ID, SDL_BUTTON_RIGHT, (buttons & (1 << 1)) != 0);
|
||||
SDL_SendMouseButton(0, mouse->focus, SDL_DEFAULT_MOUSE_ID, SDL_BUTTON_MIDDLE, (buttons & (1 << 2)) != 0);
|
||||
|
||||
if (!mouse->relative_mode) {
|
||||
const int x = (int)(Sint16)regs.x.cx; // part of function 0x3's return value.
|
||||
const int y = (int)(Sint16)regs.x.dx;
|
||||
SDL_SendMouseMotion(0, mouse->focus, SDL_DEFAULT_MOUSE_ID, false, x, y);
|
||||
} else {
|
||||
regs.x.ax = 0xB; // read motion counters
|
||||
__dpmi_int(0x33, ®s);
|
||||
// values returned here are -32768 to 32767
|
||||
const SDL_VideoData *viddata = device->internal;
|
||||
const float MICKEYS_PER_HPIXEL = viddata->mickeys_per_hpixel;
|
||||
const float MICKEYS_PER_VPIXEL = viddata->mickeys_per_vpixel;
|
||||
const int mickeys_x = (int)(Sint16)regs.x.cx;
|
||||
const int mickeys_y = (int)(Sint16)regs.x.dx;
|
||||
SDL_SendMouseMotion(0, mouse->focus, SDL_DEFAULT_MOUSE_ID, true, mickeys_x / MICKEYS_PER_HPIXEL, mickeys_y / MICKEYS_PER_VPIXEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void KeyboardIRQHandler(void) // this is wrapped in a thing that handles IRET, etc.
|
||||
{
|
||||
keyevents_ringbuffer[keyevents_head] = inportb(KBD_DATA_PORT);
|
||||
keyevents_head = (keyevents_head + 1) & (SDL_arraysize(keyevents_ringbuffer) - 1);
|
||||
DOS_EndOfInterrupt(1);
|
||||
}
|
||||
static void KeyboardIRQHandler_End(void) {} // end-of-ISR label for memory locking
|
||||
|
||||
void DOSVESA_InitKeyboard(SDL_VideoDevice *device)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
|
||||
// Lock ISR code and data to prevent page faults during interrupts
|
||||
DOS_LockCode(KeyboardIRQHandler, KeyboardIRQHandler_End);
|
||||
DOS_LockVariable(keyevents_ringbuffer);
|
||||
DOS_LockVariable(keyevents_head);
|
||||
DOS_LockVariable(keyevents_tail);
|
||||
|
||||
DOS_HookInterrupt(1, KeyboardIRQHandler, &data->keyboard_interrupt_hook);
|
||||
}
|
||||
|
||||
void DOSVESA_QuitKeyboard(SDL_VideoDevice *device)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
DOS_UnhookInterrupt(&data->keyboard_interrupt_hook, false);
|
||||
|
||||
// Drain the BIOS keyboard buffer so held keys (like ESC) don't
|
||||
// bleed through to the DOS command line after we exit.
|
||||
{
|
||||
__dpmi_regs regs;
|
||||
for (;;) {
|
||||
regs.h.ah = 0x01; // BIOS: check for keystroke
|
||||
__dpmi_int(0x16, ®s);
|
||||
if (regs.x.flags & 0x40) { // ZF set = buffer empty
|
||||
break;
|
||||
}
|
||||
regs.h.ah = 0x00; // BIOS: read keystroke (removes it)
|
||||
__dpmi_int(0x16, ®s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SDL_VIDEO_DRIVER_DOSVESA
|
||||
31
src/video/dos/SDL_dosevents_c.h
Normal file
31
src/video/dos/SDL_dosevents_c.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_dosevents_c_h_
|
||||
#define SDL_dosevents_c_h_
|
||||
|
||||
#include "SDL_dosvideo.h"
|
||||
|
||||
void DOSVESA_PumpEvents(SDL_VideoDevice *device);
|
||||
void DOSVESA_InitKeyboard(SDL_VideoDevice *device);
|
||||
void DOSVESA_QuitKeyboard(SDL_VideoDevice *device);
|
||||
|
||||
#endif // SDL_dosevents_c_h_
|
||||
969
src/video/dos/SDL_dosframebuffer.c
Normal file
969
src/video/dos/SDL_dosframebuffer.c
Normal file
@@ -0,0 +1,969 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_DOSVESA
|
||||
|
||||
#include "../../SDL_properties_c.h"
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
#include "../SDL_sysvideo.h"
|
||||
#include "SDL_dosframebuffer_c.h"
|
||||
#include "SDL_dosmodes.h"
|
||||
#include "SDL_dosmouse.h"
|
||||
#include "SDL_dosvideo.h"
|
||||
|
||||
#include <pc.h> // for inportb, outportb
|
||||
#include <sys/movedata.h> // for dosmemput (banked framebuffer writes)
|
||||
|
||||
// note that DOS_SURFACE's value is the same string that the dummy driver uses.
|
||||
#define DOS_SURFACE "SDL.internal.window.surface"
|
||||
|
||||
// Consolidated framebuffer state (DOS has only one window)
|
||||
typedef struct DOSFramebufferState
|
||||
{
|
||||
SDL_Surface *surface; // system-RAM surface (app renders here)
|
||||
SDL_Surface *lfb_surface; // LFB surface (pixels in VRAM), NULL for direct-FB path
|
||||
bool direct_fb; // true when the fast direct-FB hint path is active
|
||||
bool use_dosmemput; // true = dosmemput (banked), false = nearptr (LFB)
|
||||
int vram_pitch;
|
||||
int vram_w;
|
||||
int vram_h;
|
||||
int bpp; // bytes per pixel
|
||||
|
||||
// dosmemput path state (use_dosmemput)
|
||||
Uint32 vram_phys; // base address for dosmemput (e.g. 0xA0000)
|
||||
bool banked_multibank; // true if fb doesn't fit in one bank window
|
||||
Uint32 win_gran_bytes; // bank granularity in bytes
|
||||
Uint32 win_size_bytes; // bank window size in bytes
|
||||
Uint32 win_base; // window base address (segment << 4)
|
||||
Uint32 win_func_ptr; // real-mode far pointer to bank-switch function
|
||||
|
||||
// nearptr path state (use_dosmemput == false)
|
||||
Uint8 *vram_ptr; // nearptr into VRAM (LFB)
|
||||
|
||||
// Cached per-frame values (set at create time, avoid repeated lookups)
|
||||
Uint8 *pixels; // == surface->pixels (stable after create)
|
||||
int src_pitch; // == surface->pitch
|
||||
Uint32 fb_size; // total framebuffer bytes (pitch * h) for full-surface fast path
|
||||
bool pitches_match; // src_pitch == vram_pitch (enables single-copy fast path)
|
||||
} DOSFramebufferState;
|
||||
|
||||
static DOSFramebufferState fb_state;
|
||||
|
||||
// Convert cursor to the current format.
|
||||
static SDL_Surface *GetConvertedCursorSurface(SDL_CursorData *curdata, SDL_Surface *dst)
|
||||
{
|
||||
SDL_Palette *pal = SDL_GetSurfacePalette(dst);
|
||||
if (!pal) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Uint32 pal_version = pal ? pal->version : 0;
|
||||
|
||||
if (curdata->converted_surface &&
|
||||
curdata->converted_surface->format == dst->format &&
|
||||
curdata->converted_palette_version == pal_version) {
|
||||
return curdata->converted_surface;
|
||||
}
|
||||
|
||||
SDL_DestroySurface(curdata->converted_surface);
|
||||
curdata->converted_surface = NULL;
|
||||
|
||||
SDL_Surface *src = curdata->surface;
|
||||
SDL_assert(src->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
int w = src->w;
|
||||
int h = src->h;
|
||||
SDL_Surface *conv = SDL_CreateSurface(w, h, dst->format);
|
||||
if (!conv) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Copy the destination palette.
|
||||
SDL_Palette *conv_pal = SDL_CreateSurfacePalette(conv);
|
||||
if (conv_pal) {
|
||||
SDL_SetPaletteColors(conv_pal, pal->colors, 0, pal->ncolors);
|
||||
}
|
||||
|
||||
// Track which palette indices are used by opaque pixels.
|
||||
bool used[256];
|
||||
SDL_memset(used, 0, sizeof(used));
|
||||
|
||||
// First pass: blit with BLENDMODE_NONE to get raw color-matched indices.
|
||||
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
|
||||
SDL_BlitSurface(src, NULL, conv, NULL);
|
||||
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Mark which indices are used by non-transparent source pixels.
|
||||
for (int y = 0; y < h; y++) {
|
||||
const Uint32 *srcrow = (const Uint32 *)((const Uint8 *)src->pixels + y * src->pitch);
|
||||
const Uint8 *convrow = (const Uint8 *)conv->pixels + y * conv->pitch;
|
||||
for (int x = 0; x < w; x++) {
|
||||
Uint8 srcA = (Uint8)(srcrow[x] >> 24);
|
||||
if (srcA > 0) {
|
||||
used[convrow[x]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find an unused index for the colorkey.
|
||||
Uint32 colorkey = 0;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (!used[i]) {
|
||||
colorkey = (Uint32)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: set transparent pixels to the colorkey index.
|
||||
for (int y = 0; y < h; y++) {
|
||||
const Uint32 *srcrow = (const Uint32 *)((const Uint8 *)src->pixels + y * src->pitch);
|
||||
Uint8 *convrow = (Uint8 *)conv->pixels + y * conv->pitch;
|
||||
for (int x = 0; x < w; x++) {
|
||||
Uint8 srcA = (Uint8)(srcrow[x] >> 24);
|
||||
if (srcA == 0) {
|
||||
convrow[x] = (Uint8)colorkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetSurfaceColorKey(conv, true, colorkey);
|
||||
SDL_SetSurfaceBlendMode(conv, SDL_BLENDMODE_NONE);
|
||||
|
||||
curdata->converted_surface = conv;
|
||||
curdata->converted_palette_version = pal_version;
|
||||
return conv;
|
||||
}
|
||||
|
||||
// Invalidation (called from SetDisplayMode before freeing DPMI mapping)
|
||||
void DOSVESA_InvalidateCachedFramebuffer(void)
|
||||
{
|
||||
fb_state.direct_fb = false;
|
||||
fb_state.vram_ptr = NULL;
|
||||
fb_state.vram_phys = 0;
|
||||
fb_state.use_dosmemput = false;
|
||||
// Clear the LFB surface pointer so UpdateWindowFramebuffer won't try
|
||||
// to blit into VRAM after the DPMI mapping has been freed.
|
||||
fb_state.lfb_surface = NULL;
|
||||
}
|
||||
|
||||
// Create a system-RAM surface (with a blank palette if INDEX8 and update the VGA DAC).
|
||||
static SDL_Surface *CreateSystemSurface(SDL_VideoData *data, int w, int h, SDL_PixelFormat surface_format)
|
||||
{
|
||||
SDL_Surface *surface = SDL_CreateSurface(w, h, surface_format);
|
||||
if (!surface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// For 8-bit indexed modes, both surfaces need palettes.
|
||||
// Share the same palette object so palette updates propagate to both.
|
||||
if (surface_format == SDL_PIXELFORMAT_INDEX8) {
|
||||
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
|
||||
if (palette) {
|
||||
// Initialize palette to all-black so that transitions start
|
||||
// from black instead of flashing uninitialized (white) colors.
|
||||
SDL_Color black[256];
|
||||
SDL_memset(black, 0, sizeof(black));
|
||||
for (int i = 0; i < 256; i++) {
|
||||
black[i].a = SDL_ALPHA_OPAQUE;
|
||||
}
|
||||
SDL_SetPaletteColors(palette, black, 0, 256);
|
||||
}
|
||||
data->palette_version = 0; // force DAC update on first present
|
||||
|
||||
// Also program the VGA DAC to all-black right now, so no flash
|
||||
// of stale/white palette colors before the first present.
|
||||
outportb(VGA_DAC_WRITE_INDEX, 0);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
outportb(VGA_DAC_DATA, 0);
|
||||
outportb(VGA_DAC_DATA, 0);
|
||||
outportb(VGA_DAC_DATA, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
// Normal (non-direct-FB) path: system-RAM surface with optional LFB blit,
|
||||
// cursor compositing, palette sync, vsync, and page-flipping.
|
||||
static bool CreateNormalFramebuffer(SDL_VideoDevice *device, SDL_Window *window,
|
||||
SDL_PixelFormat *format, void **pixels, int *pitch)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
const SDL_DisplayMode *mode = &data->current_mode;
|
||||
const SDL_DisplayModeData *mdata = mode->internal;
|
||||
const SDL_PixelFormat surface_format = mode->format;
|
||||
int w, h;
|
||||
|
||||
SDL_GetWindowSizeInPixels(window, &w, &h);
|
||||
|
||||
SDL_Surface *surface = CreateSystemSurface(data, w, h, surface_format);
|
||||
if (!surface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Surface *lfb_surface = NULL;
|
||||
|
||||
if (!data->banked_mode) {
|
||||
// LFB path: Make a surface that uses video memory directly, ot let SDL do the blitting for us.
|
||||
// Point the LFB surface at the back page for tear-free double-buffering.
|
||||
int back_page = data->page_flip_available ? (1 - data->current_page) : 0;
|
||||
void *lfb_pixels = (Uint8 *)DOS_PhysicalToLinear(data->mapping.address) + data->page_offset[back_page];
|
||||
lfb_surface = SDL_CreateSurfaceFrom(mode->w, mode->h, surface_format, lfb_pixels, mdata->pitch);
|
||||
if (!lfb_surface) {
|
||||
SDL_DestroySurface(surface);
|
||||
return false;
|
||||
}
|
||||
fb_state.lfb_surface = lfb_surface;
|
||||
|
||||
// Share the palette so updates propagate to both surfaces.
|
||||
SDL_Palette *src_palette = SDL_GetSurfacePalette(surface);
|
||||
if (src_palette) {
|
||||
SDL_SetSurfacePalette(lfb_surface, src_palette);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the framebuffer completely, in case another window at a larger size was using this before us.
|
||||
if (lfb_surface) {
|
||||
SDL_ClearSurface(lfb_surface, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
// (For banked mode, the framebuffer was already zeroed in DOSVESA_SetDisplayMode.)
|
||||
|
||||
// Save the info and return!
|
||||
fb_state.surface = surface;
|
||||
SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), DOS_SURFACE, surface);
|
||||
|
||||
*format = surface_format;
|
||||
*pixels = surface->pixels;
|
||||
*pitch = surface->pitch;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DOSVESA_CreateWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch)
|
||||
{
|
||||
SDL_zero(fb_state);
|
||||
|
||||
SDL_VideoData *data = device->internal;
|
||||
const SDL_DisplayMode *mode = &data->current_mode;
|
||||
const SDL_DisplayModeData *mdata = mode->internal;
|
||||
const SDL_PixelFormat surface_format = mode->format;
|
||||
int w, h;
|
||||
|
||||
// writing to video RAM shows up as the screen refreshes, done or not, and it might have a weird pitch, so give the app a buffer of system RAM.
|
||||
SDL_GetWindowSizeInPixels(window, &w, &h);
|
||||
|
||||
// Try to set up fast path where UpdateWindowFramebuffer copies system-RAM directly to VRAM.
|
||||
if (SDL_GetHintBoolean(SDL_HINT_DOS_ALLOW_DIRECT_FRAMEBUFFER, false)) {
|
||||
int vram_pitch = mdata->pitch;
|
||||
|
||||
// Check if we have any usable VRAM access path (banked or LFB).
|
||||
bool have_vram = false;
|
||||
|
||||
if (mdata->win_a_segment && mdata->win_size > 0 &&
|
||||
(mdata->win_a_attributes & VBE_WINATTR_USABLE) == VBE_WINATTR_USABLE) {
|
||||
// Banked window available. Use dosmemput path (preferred).
|
||||
have_vram = true;
|
||||
} else if (!data->banked_mode && data->mapping.size) {
|
||||
// LFB only, use nearptr fallback.
|
||||
have_vram = true;
|
||||
}
|
||||
|
||||
if (have_vram) {
|
||||
SDL_Surface *surface = CreateSystemSurface(data, w, h, surface_format);
|
||||
if (!surface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), DOS_SURFACE, surface);
|
||||
|
||||
fb_state.surface = surface;
|
||||
fb_state.direct_fb = true;
|
||||
fb_state.vram_pitch = vram_pitch;
|
||||
fb_state.vram_w = mdata->w;
|
||||
fb_state.vram_h = mdata->h;
|
||||
fb_state.bpp = SDL_BYTESPERPIXEL(surface_format);
|
||||
fb_state.pixels = (Uint8 *)surface->pixels;
|
||||
fb_state.src_pitch = surface->pitch;
|
||||
fb_state.fb_size = (Uint32)vram_pitch * mdata->h;
|
||||
fb_state.pitches_match = (surface->pitch == vram_pitch);
|
||||
|
||||
// Prefer dosmemput via the banked VGA window (0xA0000). Real
|
||||
// hardware testing shows dosmemput is significantly faster
|
||||
// than nearptr writes to DPMI-mapped LFB, even with bank
|
||||
// switching overhead at higher resolutions.
|
||||
//
|
||||
// The banked window always maps to page 0, so page flipping is
|
||||
// not possible through this path. We accept tearing: the app
|
||||
// opted into the direct-FB hint for maximum throughput, and
|
||||
// games like Quake never used page flipping for their software
|
||||
// renderers anyway.
|
||||
if (mdata->win_a_segment && mdata->win_size > 0 &&
|
||||
(mdata->win_a_attributes & VBE_WINATTR_USABLE) == VBE_WINATTR_USABLE) {
|
||||
Uint32 win_bytes = (Uint32)mdata->win_size * 1024;
|
||||
fb_state.use_dosmemput = true;
|
||||
fb_state.vram_ptr = NULL;
|
||||
fb_state.vram_phys = (Uint32)mdata->win_a_segment << 4;
|
||||
fb_state.win_base = fb_state.vram_phys;
|
||||
fb_state.win_gran_bytes = (Uint32)mdata->win_granularity * 1024;
|
||||
fb_state.win_size_bytes = win_bytes;
|
||||
fb_state.win_func_ptr = mdata->win_func_ptr;
|
||||
fb_state.banked_multibank = (fb_state.fb_size > win_bytes);
|
||||
} else if (!data->banked_mode && data->mapping.size) {
|
||||
// Fallback: nearptr to LFB.
|
||||
int back_page = data->page_flip_available ? (1 - data->current_page) : 0;
|
||||
fb_state.vram_ptr = (Uint8 *)DOS_PhysicalToLinear(data->mapping.address) + data->page_offset[back_page];
|
||||
fb_state.vram_phys = 0;
|
||||
fb_state.use_dosmemput = false;
|
||||
fb_state.banked_multibank = false;
|
||||
} else {
|
||||
// No usable VRAM path. Shouldn't happen, but bail out.
|
||||
SDL_DestroySurface(surface);
|
||||
return CreateNormalFramebuffer(device, window, format, pixels, pitch);
|
||||
}
|
||||
*format = surface_format;
|
||||
*pixels = surface->pixels;
|
||||
*pitch = surface->pitch;
|
||||
return true;
|
||||
}
|
||||
// else: couldn't get a direct pointer, fall through to normal path.
|
||||
}
|
||||
|
||||
return CreateNormalFramebuffer(device, window, format, pixels, pitch);
|
||||
}
|
||||
|
||||
bool DOSVESA_SetWindowFramebufferVSync(SDL_VideoDevice *device, SDL_Window *window, int vsync)
|
||||
{
|
||||
if (vsync < 0) {
|
||||
return SDL_SetError("Unsupported vsync type");
|
||||
}
|
||||
SDL_WindowData *data = window->internal;
|
||||
data->framebuffer_vsync = vsync;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DOSVESA_GetWindowFramebufferVSync(SDL_VideoDevice *device, SDL_Window *window, int *vsync)
|
||||
{
|
||||
if (vsync) {
|
||||
SDL_WindowData *data = window->internal;
|
||||
*vsync = data->framebuffer_vsync;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Switch the VGA bank window if needed. Returns without doing anything
|
||||
// if the requested bank is already active.
|
||||
SDL_FORCE_INLINE void SwitchBank(int bank, int *current_bank, Uint32 win_func_ptr)
|
||||
{
|
||||
if (bank == *current_bank) {
|
||||
return;
|
||||
}
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.bx = 0; // Window A
|
||||
regs.x.dx = (Uint16)bank;
|
||||
if (win_func_ptr) {
|
||||
regs.x.cs = (Uint16)(win_func_ptr >> 16);
|
||||
regs.x.ip = (Uint16)(win_func_ptr & 0xFFFF);
|
||||
__dpmi_simulate_real_mode_procedure_retf(®s);
|
||||
} else {
|
||||
regs.x.ax = 0x4F05;
|
||||
__dpmi_int(0x10, ®s);
|
||||
}
|
||||
*current_bank = bank;
|
||||
}
|
||||
|
||||
// Copy a contiguous byte range from system RAM to VRAM through the banked
|
||||
// window, switching banks as needed.
|
||||
SDL_FORCE_INLINE void BankedDosmemput(const Uint8 *src, Uint32 total_bytes, Uint32 dst_offset,
|
||||
Uint32 win_gran_bytes, Uint32 win_size_bytes,
|
||||
Uint32 win_base, Uint32 win_func_ptr,
|
||||
int *current_bank)
|
||||
{
|
||||
Uint32 src_off = 0;
|
||||
while (total_bytes > 0) {
|
||||
int bank = (int)(dst_offset / win_gran_bytes);
|
||||
Uint32 off_in_win = dst_offset % win_gran_bytes;
|
||||
Uint32 avail = win_size_bytes - off_in_win;
|
||||
Uint32 n = total_bytes;
|
||||
if (n > avail) {
|
||||
n = avail;
|
||||
}
|
||||
SwitchBank(bank, current_bank, win_func_ptr);
|
||||
dosmemput(src + src_off, n, win_base + off_in_win);
|
||||
src_off += n;
|
||||
dst_offset += n;
|
||||
total_bytes -= n;
|
||||
}
|
||||
}
|
||||
|
||||
// Bank-switched copy of a rectangular region from a system RAM surface to the
|
||||
// VGA banked window. `src_rect` is in source-surface coordinates; `dst_x` and
|
||||
// `dst_y` give the top-left of the *surface's* position on screen (the
|
||||
// centering offset). The routine handles bank boundaries correctly even when a
|
||||
// scanline spans two banks.
|
||||
//
|
||||
// `current_bank` is an in/out parameter so that consecutive calls (one per dirty
|
||||
// rect) can avoid redundant bank switches when rects happen to fall in the same
|
||||
// bank. Initialise it to -1 before the first call.
|
||||
static void BankedFramebufferCopyRect(const SDL_DisplayModeData *mdata,
|
||||
const SDL_Surface *src,
|
||||
const SDL_Rect *src_rect,
|
||||
int dst_x, int dst_y,
|
||||
Uint32 win_gran_bytes,
|
||||
Uint32 win_size_bytes,
|
||||
Uint32 win_base,
|
||||
int *current_bank,
|
||||
Uint32 win_func_ptr)
|
||||
{
|
||||
const Uint16 dst_pitch = mdata->pitch;
|
||||
const int bytes_per_pixel = SDL_BYTESPERPIXEL(src->format);
|
||||
const int row_bytes = src_rect->w * bytes_per_pixel;
|
||||
|
||||
// Fast path: if the source row width matches src pitch AND the destination
|
||||
// row width matches dst pitch, the data is contiguous in both source and
|
||||
// destination — we can copy it as one flat block, minimizing dosmemput calls.
|
||||
if (row_bytes == src->pitch && row_bytes == dst_pitch) {
|
||||
const Uint8 *src_data = (const Uint8 *)src->pixels + src_rect->y * src->pitch + src_rect->x * bytes_per_pixel;
|
||||
Uint32 dst_offset = (Uint32)(dst_y + src_rect->y) * dst_pitch + (Uint32)(dst_x + src_rect->x) * bytes_per_pixel;
|
||||
BankedDosmemput(src_data, (Uint32)(row_bytes * src_rect->h), dst_offset,
|
||||
win_gran_bytes, win_size_bytes, win_base, win_func_ptr, current_bank);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = 0; y < src_rect->h; y++) {
|
||||
const Uint8 *src_row = (const Uint8 *)src->pixels + (src_rect->y + y) * src->pitch + src_rect->x * bytes_per_pixel;
|
||||
Uint32 dst_offset = (Uint32)(dst_y + src_rect->y + y) * dst_pitch + (Uint32)(dst_x + src_rect->x) * bytes_per_pixel;
|
||||
BankedDosmemput(src_row, (Uint32)row_bytes, dst_offset,
|
||||
win_gran_bytes, win_size_bytes, win_base, win_func_ptr, current_bank);
|
||||
}
|
||||
}
|
||||
|
||||
static void WaitForVBlank(void)
|
||||
{
|
||||
while (inportb(VGA_STATUS_PORT) & VGA_STATUS_VBLANK) {
|
||||
SDL_CPUPauseInstruction();
|
||||
}
|
||||
while (!(inportb(VGA_STATUS_PORT) & VGA_STATUS_VBLANK)) {
|
||||
SDL_CPUPauseInstruction();
|
||||
}
|
||||
}
|
||||
|
||||
static void ProgramVGADAC(SDL_Palette *palette)
|
||||
{
|
||||
outportb(VGA_DAC_WRITE_INDEX, 0);
|
||||
for (int i = 0; i < palette->ncolors && i < 256; i++) {
|
||||
outportb(VGA_DAC_DATA, palette->colors[i].r >> 2);
|
||||
outportb(VGA_DAC_DATA, palette->colors[i].g >> 2);
|
||||
outportb(VGA_DAC_DATA, palette->colors[i].b >> 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool DOSVESA_UpdateWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window, const SDL_Rect *rects, int numrects)
|
||||
{
|
||||
SDL_VideoData *vdata = device->internal;
|
||||
|
||||
// ===================================================================
|
||||
// Direct-FB fast path: copy system-RAM to VRAM, program palette,
|
||||
// optionally page-flip. No cursor compositing, no SDL_BlitSurface.
|
||||
//
|
||||
// This path prefers banked dosmemput (through the VGA window at
|
||||
// 0xA0000) over nearptr writes to the DPMI-mapped LFB. Real
|
||||
// hardware testing on multiple NVIDIA cards showed dosmemput is
|
||||
// significantly faster, even with bank-switching overhead at
|
||||
// higher resolutions. The nearptr LFB path is only used as a
|
||||
// fallback if no usable banked window is available.
|
||||
// ===================================================================
|
||||
if (fb_state.direct_fb && (fb_state.vram_ptr || fb_state.vram_phys)) {
|
||||
|
||||
// Palette update.
|
||||
if (window->surface) {
|
||||
SDL_Palette *pal = window->surface->palette;
|
||||
if (pal && pal->version != vdata->palette_version) {
|
||||
vdata->palette_version = pal->version;
|
||||
ProgramVGADAC(pal);
|
||||
}
|
||||
}
|
||||
|
||||
if (fb_state.use_dosmemput) {
|
||||
// dosmemput path (banked window, possibly multi-bank)
|
||||
const int src_pitch = fb_state.src_pitch;
|
||||
const int dst_pitch = fb_state.vram_pitch;
|
||||
const Uint8 *pixels = fb_state.pixels;
|
||||
|
||||
if (!fb_state.banked_multibank) {
|
||||
// Single-bank: entire FB fits in one window (e.g. mode 13h).
|
||||
// Full-surface fast path when pitches match.
|
||||
if (fb_state.pitches_match) {
|
||||
dosmemput(pixels, fb_state.fb_size, fb_state.vram_phys);
|
||||
} else {
|
||||
for (int i = 0; i < numrects; i++) {
|
||||
int rx = rects[i].x, ry = rects[i].y;
|
||||
int rw = rects[i].w, rh = rects[i].h;
|
||||
if (rx < 0) {
|
||||
rw += rx;
|
||||
rx = 0;
|
||||
}
|
||||
if (ry < 0) {
|
||||
rh += ry;
|
||||
ry = 0;
|
||||
}
|
||||
if (rx + rw > fb_state.vram_w) {
|
||||
rw = fb_state.vram_w - rx;
|
||||
}
|
||||
if (ry + rh > fb_state.vram_h) {
|
||||
rh = fb_state.vram_h - ry;
|
||||
}
|
||||
if (rw <= 0 || rh <= 0) {
|
||||
continue;
|
||||
}
|
||||
const int bx = rx * fb_state.bpp;
|
||||
const int bw = rw * fb_state.bpp;
|
||||
const Uint8 *sp = pixels + ry * src_pitch + bx;
|
||||
Uint32 dst_off = fb_state.vram_phys + ry * dst_pitch + bx;
|
||||
for (int row = 0; row < rh; row++) {
|
||||
dosmemput(sp, bw, dst_off);
|
||||
sp += src_pitch;
|
||||
dst_off += dst_pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multi-bank: bank-switch as needed.
|
||||
const Uint32 gran = fb_state.win_gran_bytes;
|
||||
const Uint32 wsize = fb_state.win_size_bytes;
|
||||
const Uint32 wbase = fb_state.win_base;
|
||||
const Uint32 wfunc = fb_state.win_func_ptr;
|
||||
int current_bank = -1;
|
||||
|
||||
if (fb_state.pitches_match) {
|
||||
// Contiguous: stream the entire framebuffer through banks.
|
||||
BankedDosmemput(pixels, fb_state.fb_size, 0,
|
||||
gran, wsize, wbase, wfunc, ¤t_bank);
|
||||
} else {
|
||||
// Per-rect with bank switching.
|
||||
for (int i = 0; i < numrects; i++) {
|
||||
int rx = rects[i].x, ry = rects[i].y;
|
||||
int rw = rects[i].w, rh = rects[i].h;
|
||||
if (rx < 0) {
|
||||
rw += rx;
|
||||
rx = 0;
|
||||
}
|
||||
if (ry < 0) {
|
||||
rh += ry;
|
||||
ry = 0;
|
||||
}
|
||||
if (rx + rw > fb_state.vram_w) {
|
||||
rw = fb_state.vram_w - rx;
|
||||
}
|
||||
if (ry + rh > fb_state.vram_h) {
|
||||
rh = fb_state.vram_h - ry;
|
||||
}
|
||||
if (rw <= 0 || rh <= 0) {
|
||||
continue;
|
||||
}
|
||||
const int bx = rx * fb_state.bpp;
|
||||
const int bw = rw * fb_state.bpp;
|
||||
|
||||
for (int row = 0; row < rh; row++) {
|
||||
const Uint8 *sp = pixels + (ry + row) * src_pitch + bx;
|
||||
Uint32 dst_offset = (Uint32)(ry + row) * dst_pitch + bx;
|
||||
BankedDosmemput(sp, (Uint32)bw, dst_offset,
|
||||
gran, wsize, wbase, wfunc, ¤t_bank);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// nearptr path (LFB fallback)
|
||||
if (fb_state.pitches_match) {
|
||||
SDL_memcpy(fb_state.vram_ptr, fb_state.pixels, fb_state.fb_size);
|
||||
} else {
|
||||
const int bpp = fb_state.bpp;
|
||||
const int src_pitch = fb_state.src_pitch;
|
||||
const int dst_pitch = fb_state.vram_pitch;
|
||||
const Uint8 *pixels = fb_state.pixels;
|
||||
|
||||
for (int i = 0; i < numrects; i++) {
|
||||
int rx = rects[i].x, ry = rects[i].y;
|
||||
int rw = rects[i].w, rh = rects[i].h;
|
||||
if (rx < 0) {
|
||||
rw += rx;
|
||||
rx = 0;
|
||||
}
|
||||
if (ry < 0) {
|
||||
rh += ry;
|
||||
ry = 0;
|
||||
}
|
||||
if (rx + rw > fb_state.vram_w) {
|
||||
rw = fb_state.vram_w - rx;
|
||||
}
|
||||
if (ry + rh > fb_state.vram_h) {
|
||||
rh = fb_state.vram_h - ry;
|
||||
}
|
||||
if (rw <= 0 || rh <= 0) {
|
||||
continue;
|
||||
}
|
||||
const int bw = rw * bpp;
|
||||
const Uint8 *sp = pixels + ry * src_pitch + rx * bpp;
|
||||
Uint8 *dp = fb_state.vram_ptr + ry * dst_pitch + rx * bpp;
|
||||
for (int row = 0; row < rh; row++) {
|
||||
SDL_memcpy(dp, sp, bw);
|
||||
sp += src_pitch;
|
||||
dp += dst_pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page flipping is only used with the nearptr LFB path. The banked
|
||||
// dosmemput path always writes to page 0 (the visible page) and
|
||||
// accepts tearing, avoiding a BIOS call.
|
||||
if (!fb_state.use_dosmemput && vdata->page_flip_available) {
|
||||
const SDL_DisplayModeData *mdata = vdata->current_mode.internal;
|
||||
int back_page = 1 - vdata->current_page;
|
||||
Uint16 first_scanline = (Uint16)(vdata->page_offset[back_page] / mdata->pitch);
|
||||
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F07;
|
||||
regs.x.bx = 0x0080;
|
||||
regs.x.cx = 0;
|
||||
regs.x.dx = first_scanline;
|
||||
__dpmi_int(0x10, ®s);
|
||||
|
||||
vdata->current_page = back_page;
|
||||
|
||||
int new_back = 1 - vdata->current_page;
|
||||
fb_state.vram_ptr = (Uint8 *)DOS_PhysicalToLinear(vdata->mapping.address) + vdata->page_offset[new_back];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Normal path: system-RAM to LFB surface (or banked copy)
|
||||
// with cursor compositing, palette sync, vsync, and page-flipping.
|
||||
// ===================================================================
|
||||
|
||||
SDL_Surface *src = fb_state.surface;
|
||||
if (!src) {
|
||||
src = (SDL_Surface *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), DOS_SURFACE, NULL);
|
||||
}
|
||||
if (!src) {
|
||||
return SDL_SetError("Couldn't find DOS surface for window");
|
||||
}
|
||||
|
||||
const SDL_DisplayModeData *mdata = vdata->current_mode.internal;
|
||||
SDL_WindowData *windata = window->internal;
|
||||
|
||||
// For 8-bit indexed modes, sync palette data between surfaces so the
|
||||
// blit uses the correct color mapping. The actual VGA DAC programming
|
||||
// is deferred until vertical blanking to avoid visible palette flicker.
|
||||
SDL_Palette *dac_palette = NULL;
|
||||
bool dac_needs_update = false;
|
||||
|
||||
if (src->format == SDL_PIXELFORMAT_INDEX8) {
|
||||
SDL_Palette *win_palette = window->surface ? SDL_GetSurfacePalette(window->surface) : NULL;
|
||||
SDL_Palette *src_palette = SDL_GetSurfacePalette(src);
|
||||
|
||||
// Sync: if the app set colors on the window surface palette,
|
||||
// copy them to the internal surface so the blit works correctly.
|
||||
if (win_palette && src_palette && win_palette != src_palette &&
|
||||
win_palette->version != src_palette->version) {
|
||||
SDL_SetPaletteColors(src_palette, win_palette->colors, 0,
|
||||
SDL_min(win_palette->ncolors, src_palette->ncolors));
|
||||
|
||||
if (!vdata->banked_mode && fb_state.lfb_surface) {
|
||||
// Also update the LFB surface palette for correct blitting
|
||||
SDL_Palette *dst_palette = SDL_GetSurfacePalette(fb_state.lfb_surface);
|
||||
if (dst_palette && dst_palette != src_palette) {
|
||||
SDL_SetPaletteColors(dst_palette, win_palette->colors, 0,
|
||||
SDL_min(win_palette->ncolors, dst_palette->ncolors));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether the VGA DAC needs reprogramming.
|
||||
dac_palette = win_palette ? win_palette : src_palette;
|
||||
if (dac_palette && dac_palette->version != vdata->palette_version) {
|
||||
dac_needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (vdata->banked_mode) {
|
||||
// --- Banked framebuffer path (dirty-rect aware) ---
|
||||
// We composite the cursor onto the source surface (in system RAM),
|
||||
// then bank-copy only the dirty rectangles to the VGA window. We
|
||||
// need to undo the cursor composite afterwards so the app's surface
|
||||
// isn't permanently modified.
|
||||
|
||||
const int dst_x = ((int)mdata->w - src->w) / 2;
|
||||
const int dst_y = ((int)mdata->h - src->h) / 2;
|
||||
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
SDL_Surface *cursor = NULL;
|
||||
SDL_Rect cursorrect;
|
||||
SDL_Rect cursor_clipped; // cursorrect clipped to src bounds
|
||||
SDL_Surface *cursor_save = NULL;
|
||||
bool have_cursor_rect = false;
|
||||
|
||||
SDL_Cursor *cur = mouse ? mouse->cur_cursor : NULL;
|
||||
if (cur && cur->animation) {
|
||||
cur = cur->animation->frames[cur->animation->current_frame];
|
||||
}
|
||||
if (mouse && mouse->internal && !mouse->relative_mode && mouse->cursor_visible && cur && cur->internal) {
|
||||
cursor = cur->internal->surface;
|
||||
if (cursor) {
|
||||
cursorrect.x = SDL_clamp((int)mouse->x, 0, window->w) - cur->internal->hot_x;
|
||||
cursorrect.y = SDL_clamp((int)mouse->y, 0, window->h) - cur->internal->hot_y;
|
||||
cursorrect.w = cursor->w;
|
||||
cursorrect.h = cursor->h;
|
||||
|
||||
// Clip cursor rect to src bounds for save/restore.
|
||||
cursor_clipped = cursorrect;
|
||||
if (cursor_clipped.x < 0) {
|
||||
cursor_clipped.w += cursor_clipped.x;
|
||||
cursor_clipped.x = 0;
|
||||
}
|
||||
if (cursor_clipped.y < 0) {
|
||||
cursor_clipped.h += cursor_clipped.y;
|
||||
cursor_clipped.y = 0;
|
||||
}
|
||||
if (cursor_clipped.x + cursor_clipped.w > src->w) {
|
||||
cursor_clipped.w = src->w - cursor_clipped.x;
|
||||
}
|
||||
if (cursor_clipped.y + cursor_clipped.h > src->h) {
|
||||
cursor_clipped.h = src->h - cursor_clipped.y;
|
||||
}
|
||||
|
||||
if (cursor_clipped.w > 0 && cursor_clipped.h > 0) {
|
||||
have_cursor_rect = true;
|
||||
|
||||
// Save the pixels under the cursor so we can restore them after the copy.
|
||||
cursor_save = SDL_CreateSurface(cursor_clipped.w, cursor_clipped.h, src->format);
|
||||
if (cursor_save) {
|
||||
if (src->format == SDL_PIXELFORMAT_INDEX8) {
|
||||
SDL_Palette *sp = SDL_GetSurfacePalette(src);
|
||||
if (sp) {
|
||||
SDL_SetSurfacePalette(cursor_save, sp);
|
||||
}
|
||||
}
|
||||
SDL_BlitSurface(src, &cursor_clipped, cursor_save, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Composite cursor onto the source surface.
|
||||
{
|
||||
SDL_Surface *blit_cursor = GetConvertedCursorSurface(cur->internal, src);
|
||||
SDL_BlitSurface(blit_cursor ? blit_cursor : cursor, NULL, src, &cursorrect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for vsync before the copy to reduce tearing.
|
||||
const int vsync_interval = windata->framebuffer_vsync;
|
||||
if (vsync_interval > 0 || dac_needs_update) {
|
||||
WaitForVBlank();
|
||||
}
|
||||
|
||||
if (dac_needs_update) {
|
||||
vdata->palette_version = dac_palette->version;
|
||||
ProgramVGADAC(dac_palette);
|
||||
}
|
||||
|
||||
// Bank-switched copy of only the dirty rectangles.
|
||||
// Pre-compute constants shared across all rect copies.
|
||||
const Uint32 win_gran_bytes = (Uint32)mdata->win_granularity * 1024;
|
||||
const Uint32 win_size_bytes = (Uint32)mdata->win_size * 1024;
|
||||
const Uint32 win_base = (Uint32)mdata->win_a_segment << 4;
|
||||
int current_bank = -1;
|
||||
|
||||
// Track whether the cursor region was already covered by a dirty rect
|
||||
// so we don't copy it twice.
|
||||
bool cursor_covered = false;
|
||||
|
||||
for (int r = 0; r < numrects; r++) {
|
||||
// Clip the dirty rect to the source surface bounds.
|
||||
SDL_Rect rect = rects[r];
|
||||
if (rect.x < 0) {
|
||||
rect.w += rect.x;
|
||||
rect.x = 0;
|
||||
}
|
||||
if (rect.y < 0) {
|
||||
rect.h += rect.y;
|
||||
rect.y = 0;
|
||||
}
|
||||
if (rect.x + rect.w > src->w) {
|
||||
rect.w = src->w - rect.x;
|
||||
}
|
||||
if (rect.y + rect.h > src->h) {
|
||||
rect.h = src->h - rect.y;
|
||||
}
|
||||
if (rect.w <= 0 || rect.h <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the cursor is visible, check whether this rect fully covers it.
|
||||
if (have_cursor_rect && !cursor_covered) {
|
||||
if (rect.x <= cursor_clipped.x &&
|
||||
rect.y <= cursor_clipped.y &&
|
||||
rect.x + rect.w >= cursor_clipped.x + cursor_clipped.w &&
|
||||
rect.y + rect.h >= cursor_clipped.y + cursor_clipped.h) {
|
||||
cursor_covered = true;
|
||||
}
|
||||
}
|
||||
|
||||
BankedFramebufferCopyRect(mdata, src, &rect, dst_x, dst_y,
|
||||
win_gran_bytes, win_size_bytes, win_base,
|
||||
¤t_bank, mdata->win_func_ptr);
|
||||
}
|
||||
|
||||
// If no dirty rect covered the cursor, copy the cursor region separately.
|
||||
if (have_cursor_rect && !cursor_covered) {
|
||||
BankedFramebufferCopyRect(mdata, src, &cursor_clipped, dst_x, dst_y,
|
||||
win_gran_bytes, win_size_bytes, win_base,
|
||||
¤t_bank, mdata->win_func_ptr);
|
||||
}
|
||||
|
||||
// Restore the source surface pixels under the cursor.
|
||||
if (cursor_save) {
|
||||
SDL_Rect restore_rect = cursor_clipped;
|
||||
SDL_BlitSurface(cursor_save, NULL, src, &restore_rect);
|
||||
SDL_DestroySurface(cursor_save);
|
||||
}
|
||||
|
||||
} else {
|
||||
// --- LFB path ---
|
||||
SDL_Surface *dst = fb_state.lfb_surface;
|
||||
if (!dst) {
|
||||
return SDL_SetError("Couldn't find VESA linear framebuffer surface for window");
|
||||
}
|
||||
|
||||
const SDL_Rect dstrect = { (dst->w - src->w) / 2, (dst->h - src->h) / 2, src->w, src->h };
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
SDL_Surface *cursor = NULL;
|
||||
SDL_Rect cursorrect;
|
||||
|
||||
SDL_Cursor *cur = mouse ? mouse->cur_cursor : NULL;
|
||||
if (cur && cur->animation) {
|
||||
cur = cur->animation->frames[cur->animation->current_frame];
|
||||
}
|
||||
if (mouse && mouse->internal && !mouse->relative_mode && mouse->cursor_visible && cur && cur->internal) {
|
||||
cursor = cur->internal->surface;
|
||||
if (cursor) {
|
||||
cursorrect.x = dstrect.x + SDL_clamp((int)mouse->x, 0, window->w) - cur->internal->hot_x;
|
||||
cursorrect.y = dstrect.y + SDL_clamp((int)mouse->y, 0, window->h) - cur->internal->hot_y;
|
||||
}
|
||||
}
|
||||
|
||||
// If both surfaces are INDEX8 and same size, skip the SDL blit
|
||||
// machinery (which does per-pixel palette remapping even for
|
||||
// identical palettes), copy directly to the LFB surface using
|
||||
// SDL_memcpy.
|
||||
if (src->format == SDL_PIXELFORMAT_INDEX8 &&
|
||||
dst->format == SDL_PIXELFORMAT_INDEX8 &&
|
||||
src->w == dstrect.w && src->h == dstrect.h) {
|
||||
const Uint8 *sp = (const Uint8 *)src->pixels;
|
||||
Uint8 *dp = (Uint8 *)dst->pixels + dstrect.y * dst->pitch + dstrect.x;
|
||||
if (src->pitch == dstrect.w && dst->pitch == dstrect.w) {
|
||||
SDL_memcpy(dp, sp, (size_t)dstrect.w * dstrect.h);
|
||||
} else {
|
||||
for (int row = 0; row < dstrect.h; row++) {
|
||||
SDL_memcpy(dp, sp, dstrect.w);
|
||||
sp += src->pitch;
|
||||
dp += dst->pitch;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Blit to the back page (or the only page, if no page-flipping)
|
||||
if (!SDL_BlitSurface(src, NULL, dst, &dstrect)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
SDL_Surface *blit_cursor = GetConvertedCursorSurface(cur->internal, dst);
|
||||
if (!SDL_BlitSurface(blit_cursor ? blit_cursor : cursor, NULL, dst, &cursorrect)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (vdata->page_flip_available) {
|
||||
// Page-flip with optional vsync.
|
||||
const int vsync_interval = windata->framebuffer_vsync;
|
||||
int back_page = 1 - vdata->current_page;
|
||||
Uint16 first_scanline = (Uint16)(vdata->page_offset[back_page] / mdata->pitch);
|
||||
|
||||
if (vsync_interval > 0 || dac_needs_update) {
|
||||
// Wait for vblank so the flip and DAC update appear together.
|
||||
WaitForVBlank();
|
||||
}
|
||||
|
||||
if (dac_needs_update) {
|
||||
vdata->palette_version = dac_palette->version;
|
||||
ProgramVGADAC(dac_palette);
|
||||
}
|
||||
|
||||
// Flip: make the back page (which we just drew to) the visible page.
|
||||
// Always use subfunction 0x0080 (set display start, don't wait) —
|
||||
// vsync is controlled by our manual vblank wait above.
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F07;
|
||||
regs.x.bx = 0x0080;
|
||||
regs.x.cx = 0; // first pixel in scan line
|
||||
regs.x.dx = first_scanline;
|
||||
__dpmi_int(0x10, ®s);
|
||||
|
||||
vdata->current_page = back_page;
|
||||
|
||||
// Update LFB surface to point at the new back page (the old front page)
|
||||
int new_back = 1 - vdata->current_page;
|
||||
dst->pixels = (Uint8 *)DOS_PhysicalToLinear(vdata->mapping.address) + vdata->page_offset[new_back];
|
||||
} else {
|
||||
// No page-flipping: wait for vsync, then update DAC atomically
|
||||
const int vsync_interval = windata->framebuffer_vsync;
|
||||
if (vsync_interval > 0 || dac_needs_update) {
|
||||
WaitForVBlank();
|
||||
}
|
||||
|
||||
if (dac_needs_update) {
|
||||
vdata->palette_version = dac_palette->version;
|
||||
ProgramVGADAC(dac_palette);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DOSVESA_DestroyWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window)
|
||||
{
|
||||
// Note: we intentionally do NOT call SDL_ClearSurface on the LFB surface
|
||||
// here. SetDisplayMode already blanks the framebuffer (both pages) when
|
||||
// setting a new mode, and the LFB surface's pixels pointer may be stale
|
||||
// if the DPMI mapping was freed before this function is called.
|
||||
(void)device;
|
||||
SDL_zero(fb_state);
|
||||
SDL_ClearProperty(SDL_GetWindowProperties(window), DOS_SURFACE);
|
||||
}
|
||||
|
||||
#endif // SDL_VIDEO_DRIVER_DOSVESA
|
||||
34
src/video/dos/SDL_dosframebuffer_c.h
Normal file
34
src/video/dos/SDL_dosframebuffer_c.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_dosframebuffer_c_h_
|
||||
#define SDL_dosframebuffer_c_h_
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
extern bool DOSVESA_CreateWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch);
|
||||
extern bool DOSVESA_UpdateWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window, const SDL_Rect *rects, int numrects);
|
||||
extern void DOSVESA_DestroyWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window);
|
||||
extern bool DOSVESA_SetWindowFramebufferVSync(SDL_VideoDevice *device, SDL_Window *window, int vsync);
|
||||
extern bool DOSVESA_GetWindowFramebufferVSync(SDL_VideoDevice *device, SDL_Window *window, int *vsync);
|
||||
extern void DOSVESA_InvalidateCachedFramebuffer(void);
|
||||
|
||||
#endif // SDL_dosframebuffer_c_h_
|
||||
688
src/video/dos/SDL_dosmodes.c
Normal file
688
src/video/dos/SDL_dosmodes.c
Normal file
@@ -0,0 +1,688 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_DOSVESA
|
||||
|
||||
// SDL internals
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
#include "../SDL_sysvideo.h"
|
||||
#include "SDL_dosframebuffer_c.h"
|
||||
|
||||
// DOS declarations
|
||||
#include "SDL_dosmodes.h"
|
||||
#include "SDL_dosvideo.h"
|
||||
#include <sys/movedata.h> // for dosmemput (banked framebuffer access)
|
||||
|
||||
// Some VESA usage information:
|
||||
// https://delorie.com/djgpp/doc/ug/graphics/vesa.html.en
|
||||
// https://delorie.com/djgpp/doc/ug/graphics/vbe20.html
|
||||
// https://wiki.osdev.org/User:Omarrx024/VESA_Tutorial
|
||||
// https://wiki.osdev.org/VESA_Video_Modes
|
||||
// https://www.phatcode.net/res/221/files/vbe20.pdf
|
||||
|
||||
// VBE mode attribute bits (ModeAttributes field)
|
||||
#define VBE_MODEATTR_SUPPORTED 0x01 // bit 0: mode supported in hardware
|
||||
#define VBE_MODEATTR_COLOR 0x08 // bit 3: color mode (vs. monochrome)
|
||||
#define VBE_MODEATTR_GRAPHICS 0x10 // bit 4: graphics mode (vs. text)
|
||||
#define VBE_MODEATTR_LFB 0x80 // bit 7: linear framebuffer available
|
||||
|
||||
// Minimum required mode attributes for usable graphics modes
|
||||
#define VBE_MODEATTR_REQUIRED (VBE_MODEATTR_SUPPORTED | VBE_MODEATTR_COLOR | VBE_MODEATTR_GRAPHICS)
|
||||
|
||||
// VBE memory model types (MemoryModel field)
|
||||
#define VBE_MEMMODEL_PACKED_PIXEL 4 // packed pixel (includes 8-bit indexed)
|
||||
#define VBE_MEMMODEL_DIRECT_COLOR 6 // direct color (RGB with masks)
|
||||
|
||||
// VBE set-mode flag
|
||||
#define VBE_SETMODE_LFB 0x4000 // request linear framebuffer when setting mode
|
||||
|
||||
// VBE mode list sentinel
|
||||
#define VBE_MODELIST_END 0xFFFF
|
||||
|
||||
// VGA mode 13h (320x200x256) is universally supported, but most VESA
|
||||
// BIOSes do not include it in their VESA mode list. This sentinel
|
||||
// value is used to identify the legacy fallback.
|
||||
#define VGA_MODE_13H_SENTINEL 0xFFEE
|
||||
#define VGA_MODE_13H_SEGMENT 0xA000
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// this is the struct from the hardware; we save only a few parts from this, later, in SDL_VESAInfo.
|
||||
typedef struct SDL_VESAHardwareInfo
|
||||
{
|
||||
Uint8 VESASignature[4];
|
||||
Uint16 VESAVersion;
|
||||
Uint32 OEMStringPtr; // segment:offset
|
||||
Uint8 Capabilities[4];
|
||||
Uint32 VideoModePtr; // segment:offset
|
||||
Uint16 TotalMemory;
|
||||
Uint16 OEMSoftwareRev;
|
||||
Uint32 OEMVendorNamePtr; // segment:offset
|
||||
Uint32 OEMProductNamePtr; // segment:offset
|
||||
Uint32 OEMProductRevPtr; // segment:offset
|
||||
Uint8 Reserved[222];
|
||||
Uint8 OemData[256];
|
||||
} SDL_VESAHardwareInfo;
|
||||
|
||||
typedef struct SDL_VESAModeHardwareInfo
|
||||
{
|
||||
Uint16 ModeAttributes;
|
||||
Uint8 WinAAttributes;
|
||||
Uint8 WinBAttributes;
|
||||
Uint16 WinGranularity;
|
||||
Uint16 WinSize;
|
||||
Uint16 WinASegment;
|
||||
Uint16 WinBSegment;
|
||||
Uint32 WinFuncPtr;
|
||||
Uint16 BytesPerScanLine;
|
||||
Uint16 XResolution;
|
||||
Uint16 YResolution;
|
||||
Uint8 XCharSize;
|
||||
Uint8 YCharSize;
|
||||
Uint8 NumberOfPlanes;
|
||||
Uint8 BitsPerPixel;
|
||||
Uint8 NumberOfBanks;
|
||||
Uint8 MemoryModel;
|
||||
Uint8 BankSize;
|
||||
Uint8 NumberOfImagePages;
|
||||
Uint8 Reserved_page;
|
||||
Uint8 RedMaskSize;
|
||||
Uint8 RedMaskPos;
|
||||
Uint8 GreenMaskSize;
|
||||
Uint8 GreenMaskPos;
|
||||
Uint8 BlueMaskSize;
|
||||
Uint8 BlueMaskPos;
|
||||
Uint8 ReservedMaskSize;
|
||||
Uint8 ReservedMaskPos;
|
||||
Uint8 DirectColorModeInfo;
|
||||
Uint32 PhysBasePtr;
|
||||
Uint32 OffScreenMemOffset;
|
||||
Uint16 OffScreenMemSize;
|
||||
Uint8 Reserved[206];
|
||||
} SDL_VESAModeHardwareInfo;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct SDL_VESAInfo
|
||||
{
|
||||
Uint16 version; // 0x200 == 2.0, etc.
|
||||
Uint32 total_memory; // in bytes (SDL_VESAHardwareInfo::TotalMemory does it in 64k pages).
|
||||
Uint32 video_addr_segoffset; // real mode segment:offset (only valid while VBE info block is allocated!)
|
||||
Uint16 *mode_list; // copied out of conventional memory before freeing
|
||||
int num_modes;
|
||||
Uint16 oem_software_revision;
|
||||
char *oem_string;
|
||||
char *oem_vendor;
|
||||
char *oem_product;
|
||||
char *oem_revision;
|
||||
} SDL_VESAInfo;
|
||||
|
||||
static SDL_VESAInfo *vesa_info = NULL;
|
||||
|
||||
void DOSVESA_FreeVESAInfo(void)
|
||||
{
|
||||
if (vesa_info) {
|
||||
SDL_free(vesa_info->mode_list);
|
||||
SDL_free(vesa_info->oem_string);
|
||||
SDL_free(vesa_info->oem_vendor);
|
||||
SDL_free(vesa_info->oem_product);
|
||||
SDL_free(vesa_info->oem_revision);
|
||||
SDL_free(vesa_info);
|
||||
vesa_info = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const SDL_VESAInfo *GetVESAInfo(void)
|
||||
{
|
||||
if (vesa_info) {
|
||||
return vesa_info;
|
||||
}
|
||||
|
||||
_go32_dpmi_seginfo hwinfo_seginfo;
|
||||
SDL_VESAHardwareInfo *hwinfo = (SDL_VESAHardwareInfo *)DOS_AllocateConventionalMemory(sizeof(*hwinfo), &hwinfo_seginfo);
|
||||
if (!hwinfo) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_zerop(hwinfo);
|
||||
SDL_memcpy(hwinfo->VESASignature, "VBE2", 4);
|
||||
|
||||
__dpmi_regs regs;
|
||||
regs.x.ax = 0x4F00;
|
||||
regs.x.es = DOS_LinearToPhysical(hwinfo) / 16;
|
||||
regs.x.di = DOS_LinearToPhysical(hwinfo) & 0xF;
|
||||
__dpmi_int(0x10, ®s);
|
||||
|
||||
// al is 0x4F if VESA is supported, ah is 0x00 if this specific call succeeded.
|
||||
// If the interrupt call didn't replace VESASignature with "VESA" then something went wrong, too.
|
||||
if ((regs.x.ax != 0x004F) || (SDL_memcmp(hwinfo->VESASignature, "VESA", 4) != 0)) {
|
||||
SDL_SetError("VESA video not supported on this system");
|
||||
} else {
|
||||
vesa_info = (SDL_VESAInfo *)SDL_calloc(1, sizeof(*vesa_info));
|
||||
if (vesa_info) {
|
||||
vesa_info->version = hwinfo->VESAVersion;
|
||||
vesa_info->total_memory = ((Uint32)hwinfo->TotalMemory) * (64 * 1024); // TotalMemory is 64k chunks, convert to bytes.
|
||||
vesa_info->video_addr_segoffset = hwinfo->VideoModePtr;
|
||||
vesa_info->oem_software_revision = hwinfo->OEMSoftwareRev;
|
||||
// these strings are often empty (or maybe NULL), but it's fine. We don't _actually_ need them.
|
||||
vesa_info->oem_string = DOS_GetFarPtrCString(hwinfo->OEMStringPtr);
|
||||
vesa_info->oem_vendor = DOS_GetFarPtrCString(hwinfo->OEMVendorNamePtr);
|
||||
vesa_info->oem_product = DOS_GetFarPtrCString(hwinfo->OEMProductNamePtr);
|
||||
vesa_info->oem_revision = DOS_GetFarPtrCString(hwinfo->OEMProductRevPtr);
|
||||
|
||||
// Copy the mode list out of conventional memory BEFORE freeing
|
||||
// the VBE info block. Some VESA BIOSes store the mode list
|
||||
// inside the 512-byte info block itself. If we free the block
|
||||
// first, the mode list pointer becomes dangling and we read
|
||||
// garbage, silently losing modes.
|
||||
{
|
||||
Uint32 segoffset = vesa_info->video_addr_segoffset;
|
||||
int count = 0;
|
||||
while (DOS_PeekUint16(segoffset + count * sizeof(Uint16)) != VBE_MODELIST_END) {
|
||||
count++;
|
||||
if (count > 64)
|
||||
break; // sanity limit
|
||||
}
|
||||
vesa_info->num_modes = count;
|
||||
vesa_info->mode_list = (Uint16 *)SDL_malloc(count * sizeof(Uint16));
|
||||
if (vesa_info->mode_list) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
vesa_info->mode_list[i] = DOS_PeekUint16(segoffset + i * sizeof(Uint16));
|
||||
}
|
||||
} else {
|
||||
vesa_info->num_modes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DOS_FreeConventionalMemory(&hwinfo_seginfo);
|
||||
|
||||
return vesa_info;
|
||||
}
|
||||
|
||||
// Test by writing and reading back the DAC Pixel Mask register
|
||||
// On VGA this is a read/write register, on EGA/CGA the port either
|
||||
// doesn't exist (reads 0xFF) or isn't writable.
|
||||
static bool DetectVGA(void)
|
||||
{
|
||||
const Uint8 original = inportb(VGA_DAC_PIXEL_MASK);
|
||||
outportb(VGA_DAC_PIXEL_MASK, 0xA5);
|
||||
(void)inportb(0x80); // small I/O delay
|
||||
const Uint8 readback = inportb(VGA_DAC_PIXEL_MASK);
|
||||
outportb(VGA_DAC_PIXEL_MASK, original);
|
||||
|
||||
return (readback == 0xA5);
|
||||
}
|
||||
|
||||
bool DOSVESA_SupportsVESA(void)
|
||||
{
|
||||
// We need at least VGA hardware (for mode 13h). EGA and CGA cards
|
||||
// do not support 256 colors or programmable palettes.
|
||||
if (!DetectVGA()) {
|
||||
return SDL_SetError("No VGA card detected");
|
||||
}
|
||||
|
||||
// Cache VESA info if available (NULL mean we only have VGA).
|
||||
(void)GetVESAInfo();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *DOSVESA_GetGPUName(void)
|
||||
{
|
||||
const SDL_VESAInfo *vinfo = GetVESAInfo();
|
||||
if (!vinfo) {
|
||||
return "VGA";
|
||||
}
|
||||
|
||||
if (vinfo->oem_product && *vinfo->oem_product) {
|
||||
return vinfo->oem_product;
|
||||
}
|
||||
if (vinfo->oem_vendor && *vinfo->oem_vendor) {
|
||||
return vinfo->oem_vendor;
|
||||
}
|
||||
if (vinfo->oem_string && *vinfo->oem_string) {
|
||||
return vinfo->oem_string;
|
||||
}
|
||||
|
||||
return "VESA";
|
||||
}
|
||||
|
||||
Uint32 DOSVESA_GetVESATotalMemory(void)
|
||||
{
|
||||
const SDL_VESAInfo *info = GetVESAInfo();
|
||||
return info ? info->total_memory : 0;
|
||||
}
|
||||
|
||||
static bool GetVESAModeInfo(Uint16 mode_id, SDL_DisplayModeData *info)
|
||||
{
|
||||
_go32_dpmi_seginfo hwinfo_seginfo;
|
||||
SDL_VESAModeHardwareInfo *hwinfo = (SDL_VESAModeHardwareInfo *)DOS_AllocateConventionalMemory(sizeof(*hwinfo), &hwinfo_seginfo);
|
||||
if (!hwinfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_zerop(hwinfo);
|
||||
|
||||
__dpmi_regs regs;
|
||||
regs.x.ax = 0x4F01;
|
||||
regs.x.es = DOS_LinearToPhysical(hwinfo) / 16;
|
||||
regs.x.di = DOS_LinearToPhysical(hwinfo) & 0xF;
|
||||
regs.x.cx = mode_id;
|
||||
__dpmi_int(0x10, ®s);
|
||||
|
||||
const bool retval = (regs.x.ax == 0x004F);
|
||||
if (retval) {
|
||||
SDL_zerop(info);
|
||||
info->mode_id = mode_id;
|
||||
info->attributes = hwinfo->ModeAttributes;
|
||||
info->pitch = hwinfo->BytesPerScanLine;
|
||||
info->w = hwinfo->XResolution;
|
||||
info->h = hwinfo->YResolution;
|
||||
info->num_planes = hwinfo->NumberOfPlanes;
|
||||
info->bpp = hwinfo->BitsPerPixel;
|
||||
info->memory_model = hwinfo->MemoryModel;
|
||||
info->num_image_pages = hwinfo->NumberOfImagePages;
|
||||
info->red_mask_size = hwinfo->RedMaskSize;
|
||||
info->red_mask_pos = hwinfo->RedMaskPos;
|
||||
info->green_mask_size = hwinfo->GreenMaskSize;
|
||||
info->green_mask_pos = hwinfo->GreenMaskPos;
|
||||
info->blue_mask_size = hwinfo->BlueMaskSize;
|
||||
info->blue_mask_pos = hwinfo->BlueMaskPos;
|
||||
info->physical_base_addr = hwinfo->PhysBasePtr;
|
||||
|
||||
// VBE 1.2 banked framebuffer fields
|
||||
info->has_lfb = (hwinfo->ModeAttributes & VBE_MODEATTR_LFB) != 0;
|
||||
info->win_granularity = hwinfo->WinGranularity;
|
||||
info->win_size = hwinfo->WinSize;
|
||||
info->win_a_segment = hwinfo->WinASegment;
|
||||
info->win_func_ptr = hwinfo->WinFuncPtr;
|
||||
info->win_a_attributes = hwinfo->WinAAttributes;
|
||||
}
|
||||
|
||||
DOS_FreeConventionalMemory(&hwinfo_seginfo);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DOSVESA_GetDisplayModes(SDL_VideoDevice *device, SDL_VideoDisplay *sdl_display)
|
||||
{
|
||||
const SDL_VESAInfo *vinfo = GetVESAInfo();
|
||||
|
||||
// Enumerate VESA modes if we have a VBE 1.2+ BIOS. Older VBE versions
|
||||
// (1.0, 1.1) or no VESA at all just skip this loop and fall through to
|
||||
// the VGA mode 13h fallback below.
|
||||
int num_vesa_modes = (vinfo && vinfo->version >= 0x102) ? vinfo->num_modes : 0;
|
||||
|
||||
for (int mi = 0; mi < num_vesa_modes; mi++) {
|
||||
const Uint16 modeid = vinfo->mode_list[mi];
|
||||
|
||||
SDL_DisplayModeData info;
|
||||
if (!GetVESAModeInfo(modeid, &info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip 320x200x8 from VESA. We always want VGA mode 13h for this
|
||||
// resolution (no page flip, no LFB overhead, universal compatibility).
|
||||
if (info.bpp == 8 && info.w == 320 && info.h == 200) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((info.attributes & VBE_MODEATTR_REQUIRED) != VBE_MODEATTR_REQUIRED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(info.attributes & VBE_MODEATTR_LFB) && (info.win_a_attributes & VBE_WINATTR_USABLE) != VBE_WINATTR_USABLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.num_planes != 1) {
|
||||
continue; // skip planar pixel layouts.
|
||||
} else if (info.bpp < 8) {
|
||||
continue; // skip anything below 8-bit.
|
||||
} else if (!info.w || !info.h) {
|
||||
continue; // zero-area display mode?!
|
||||
} else if (!info.has_lfb && !info.win_granularity) {
|
||||
continue; // banked mode with zero granularity would cause division by zero.
|
||||
} else if ((info.memory_model != VBE_MEMMODEL_PACKED_PIXEL) && (info.memory_model != VBE_MEMMODEL_DIRECT_COLOR)) {
|
||||
continue; // must be either packed pixel or Direct Color.
|
||||
// Note: 8-bit indexed modes are packed pixel.
|
||||
}
|
||||
|
||||
SDL_PixelFormat format = SDL_PIXELFORMAT_UNKNOWN;
|
||||
if (info.memory_model == VBE_MEMMODEL_PACKED_PIXEL) {
|
||||
switch (info.bpp) {
|
||||
case 8:
|
||||
format = SDL_PIXELFORMAT_INDEX8;
|
||||
break;
|
||||
case 15:
|
||||
format = SDL_PIXELFORMAT_XRGB1555;
|
||||
break;
|
||||
case 16:
|
||||
format = SDL_PIXELFORMAT_RGB565;
|
||||
break;
|
||||
case 24:
|
||||
format = SDL_PIXELFORMAT_RGB24;
|
||||
break;
|
||||
case 32:
|
||||
format = SDL_PIXELFORMAT_XRGB8888;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
SDL_assert(info.memory_model == VBE_MEMMODEL_DIRECT_COLOR);
|
||||
const Uint32 rmask = ((((Uint32)1) << info.red_mask_size) - 1) << info.red_mask_pos;
|
||||
const Uint32 gmask = ((((Uint32)1) << info.green_mask_size) - 1) << info.green_mask_pos;
|
||||
const Uint32 bmask = ((((Uint32)1) << info.blue_mask_size) - 1) << info.blue_mask_pos;
|
||||
format = SDL_GetPixelFormatForMasks(info.bpp, rmask, gmask, bmask, 0x00000000);
|
||||
}
|
||||
|
||||
if (format == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue; // don't know what to do with this one.
|
||||
}
|
||||
|
||||
SDL_DisplayModeData *internal = (SDL_DisplayModeData *)SDL_malloc(sizeof(*internal));
|
||||
if (!internal) {
|
||||
continue; // oof.
|
||||
}
|
||||
|
||||
SDL_copyp(internal, &info);
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
SDL_zero(mode);
|
||||
mode.format = format;
|
||||
mode.w = (int)info.w;
|
||||
mode.h = (int)info.h;
|
||||
mode.pixel_density = 1.0f; // no HighDPI scaling here.
|
||||
|
||||
// !!! FIXME: we need to parse EDID data (VESA function 0x4F15, subfunction 0x01) to get refresh rates. Leaving as 0 for now.
|
||||
// float refresh_rate; /**< refresh rate (or 0.0f for unspecified) */
|
||||
// int refresh_rate_numerator; /**< precise refresh rate numerator (or 0 for unspecified) */
|
||||
// int refresh_rate_denominator; /**< precise refresh rate denominator */
|
||||
|
||||
mode.internal = internal;
|
||||
|
||||
if (!SDL_AddFullscreenDisplayMode(sdl_display, &mode)) {
|
||||
SDL_free(internal); // oh well, carry on without it.
|
||||
}
|
||||
}
|
||||
|
||||
// Always add VGA mode 13h for 320x200x8. We skipped any VESA 320x200x8
|
||||
// modes above so this is the only entry at that resolution.
|
||||
{
|
||||
SDL_DisplayModeData *internal = (SDL_DisplayModeData *)SDL_malloc(sizeof(*internal));
|
||||
if (internal) {
|
||||
SDL_zerop(internal);
|
||||
internal->mode_id = VGA_MODE_13H_SENTINEL;
|
||||
internal->attributes = VBE_MODEATTR_REQUIRED;
|
||||
internal->pitch = 320;
|
||||
internal->w = 320;
|
||||
internal->h = 200;
|
||||
internal->num_planes = 1;
|
||||
internal->bpp = 8;
|
||||
internal->memory_model = VBE_MEMMODEL_PACKED_PIXEL;
|
||||
internal->num_image_pages = 0; // no page flipping in mode 13h
|
||||
internal->physical_base_addr = 0;
|
||||
internal->has_lfb = false;
|
||||
internal->win_granularity = 64;
|
||||
internal->win_size = 64;
|
||||
internal->win_a_segment = VGA_MODE_13H_SEGMENT;
|
||||
internal->win_func_ptr = 0;
|
||||
internal->win_a_attributes = VBE_WINATTR_USABLE;
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
SDL_zero(mode);
|
||||
mode.format = SDL_PIXELFORMAT_INDEX8;
|
||||
mode.w = 320;
|
||||
mode.h = 200;
|
||||
mode.pixel_density = 1.0f;
|
||||
mode.internal = internal;
|
||||
|
||||
if (!SDL_AddFullscreenDisplayMode(sdl_display, &mode)) {
|
||||
SDL_free(internal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort modes descending: largest to smallest (by width first, then height, then bpp).
|
||||
for (int i = 0; i < sdl_display->num_fullscreen_modes - 1; i++) {
|
||||
for (int j = i + 1; j < sdl_display->num_fullscreen_modes; j++) {
|
||||
const SDL_DisplayMode *a = &sdl_display->fullscreen_modes[i];
|
||||
const SDL_DisplayMode *b = &sdl_display->fullscreen_modes[j];
|
||||
bool swap = false;
|
||||
if (b->w > a->w) {
|
||||
swap = true;
|
||||
} else if (b->w == a->w) {
|
||||
if (b->h > a->h) {
|
||||
swap = true;
|
||||
} else if (b->h == a->h) {
|
||||
if (SDL_BITSPERPIXEL(b->format) > SDL_BITSPERPIXEL(a->format)) {
|
||||
swap = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (swap) {
|
||||
SDL_DisplayMode tmp = sdl_display->fullscreen_modes[i];
|
||||
sdl_display->fullscreen_modes[i] = sdl_display->fullscreen_modes[j];
|
||||
sdl_display->fullscreen_modes[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DOSVESA_SetDisplayMode(SDL_VideoDevice *device, SDL_VideoDisplay *sdl_display, SDL_DisplayMode *mode)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
const SDL_DisplayModeData *modedata = mode->internal;
|
||||
|
||||
if (data->current_mode.internal && (data->current_mode.internal->mode_id == modedata->mode_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DOSVESA_InvalidateCachedFramebuffer();
|
||||
|
||||
if (data->mapping.size) {
|
||||
__dpmi_free_physical_address_mapping(&data->mapping); // dump existing video mapping.
|
||||
SDL_zero(data->mapping);
|
||||
}
|
||||
|
||||
__dpmi_regs regs;
|
||||
|
||||
if (modedata->mode_id == VGA_MODE_13H_SENTINEL) {
|
||||
// Set VGA mode 13h (320x200x256) via legacy BIOS call.
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x0013;
|
||||
__dpmi_int(0x10, ®s);
|
||||
// Mode 13h always succeeds on VGA hardware; no status to check.
|
||||
|
||||
data->banked_mode = true; // uses A000:0000 segment, no LFB
|
||||
|
||||
SDL_copyp(&data->current_mode, mode);
|
||||
|
||||
data->page_flip_available = false;
|
||||
data->current_page = 0;
|
||||
data->page_offset[0] = 0;
|
||||
data->page_offset[1] = 0;
|
||||
|
||||
// Clear the framebuffer
|
||||
{
|
||||
Uint8 zero_buf[320];
|
||||
SDL_memset(zero_buf, 0, sizeof(zero_buf));
|
||||
Uint32 vga_base = (Uint32)VGA_MODE_13H_SEGMENT << 4;
|
||||
for (int row = 0; row < 200; row++) {
|
||||
dosmemput(zero_buf, 320, vga_base + row * 320);
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_GetMouse()->internal != NULL) {
|
||||
regs.x.ax = 0x7;
|
||||
regs.x.cx = 0;
|
||||
regs.x.dx = (Uint16)(mode->w - 1);
|
||||
__dpmi_int(0x33, ®s);
|
||||
|
||||
regs.x.ax = 0x8;
|
||||
regs.x.cx = 0;
|
||||
regs.x.dx = (Uint16)(mode->h - 1);
|
||||
__dpmi_int(0x33, ®s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// When the direct-FB hint is active, prefer banked mode. This needs
|
||||
// to be set explicitly for some cards (Intel 740).
|
||||
const bool is_banked_usable = modedata->win_a_segment &&
|
||||
modedata->win_size > 0 &&
|
||||
(modedata->win_a_attributes & VBE_WINATTR_USABLE) == VBE_WINATTR_USABLE;
|
||||
const bool use_lfb = modedata->has_lfb &&
|
||||
(!is_banked_usable || !SDL_GetHintBoolean(SDL_HINT_DOS_ALLOW_DIRECT_FRAMEBUFFER, false));
|
||||
|
||||
regs.x.ax = 0x4F02;
|
||||
regs.x.bx = modedata->mode_id | (use_lfb ? VBE_SETMODE_LFB : 0);
|
||||
__dpmi_int(0x10, ®s);
|
||||
|
||||
if (regs.x.ax != 0x004F) {
|
||||
return SDL_SetError("Failed to set VESA video mode");
|
||||
}
|
||||
|
||||
data->banked_mode = !use_lfb;
|
||||
|
||||
if (use_lfb) {
|
||||
data->mapping.address = modedata->physical_base_addr;
|
||||
data->mapping.size = DOSVESA_GetVESATotalMemory();
|
||||
if (__dpmi_physical_address_mapping(&data->mapping) != 0) {
|
||||
SDL_zero(data->mapping);
|
||||
regs.x.ax = 0x03; // try to dump us back into text mode. Not sure if this is a good idea, though.
|
||||
__dpmi_int(0x10, ®s);
|
||||
SDL_zero(data->current_mode);
|
||||
return SDL_SetError("Failed to map VESA video memory");
|
||||
}
|
||||
|
||||
// make sure framebuffer is blanked out.
|
||||
SDL_memset(DOS_PhysicalToLinear(data->mapping.address), '\0', (Uint32)modedata->h * (Uint32)modedata->pitch);
|
||||
} else {
|
||||
// Banked mode: no physical address mapping needed.
|
||||
// Blank the visible framebuffer through the banked window.
|
||||
Uint32 total_bytes = (Uint32)modedata->h * (Uint32)modedata->pitch;
|
||||
Uint32 win_gran_bytes = (Uint32)modedata->win_granularity * 1024;
|
||||
Uint32 win_size_bytes = (Uint32)modedata->win_size * 1024;
|
||||
Uint32 win_base = (Uint32)modedata->win_a_segment << 4;
|
||||
Uint8 zero_buf[1024];
|
||||
SDL_memset(zero_buf, 0, sizeof(zero_buf));
|
||||
|
||||
Uint32 offset = 0;
|
||||
int current_bank = -1;
|
||||
while (offset < total_bytes) {
|
||||
int bank = (int)(offset / win_gran_bytes);
|
||||
Uint32 off_in_win = offset % win_gran_bytes;
|
||||
Uint32 n = win_size_bytes - off_in_win;
|
||||
if (n > total_bytes - offset) {
|
||||
n = total_bytes - offset;
|
||||
}
|
||||
|
||||
if (bank != current_bank) {
|
||||
__dpmi_regs bregs;
|
||||
SDL_zero(bregs);
|
||||
bregs.x.bx = 0; // Window A
|
||||
bregs.x.dx = (Uint16)bank;
|
||||
if (modedata->win_func_ptr) {
|
||||
// Call WinFuncPtr directly — faster than INT 10h.
|
||||
bregs.x.cs = (Uint16)(modedata->win_func_ptr >> 16);
|
||||
bregs.x.ip = (Uint16)(modedata->win_func_ptr & 0xFFFF);
|
||||
__dpmi_simulate_real_mode_procedure_retf(&bregs);
|
||||
} else {
|
||||
bregs.x.ax = 0x4F05;
|
||||
__dpmi_int(0x10, &bregs);
|
||||
}
|
||||
current_bank = bank;
|
||||
}
|
||||
|
||||
// Zero in 1KB chunks via dosmemput
|
||||
Uint32 written = 0;
|
||||
while (written < n) {
|
||||
Uint32 chunk = n - written;
|
||||
if (chunk > sizeof(zero_buf)) {
|
||||
chunk = sizeof(zero_buf);
|
||||
}
|
||||
dosmemput(zero_buf, chunk, win_base + off_in_win + written);
|
||||
written += chunk;
|
||||
}
|
||||
offset += n;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_copyp(&data->current_mode, mode);
|
||||
|
||||
// Set up page-flipping if the mode has at least 1 image page (meaning 2 total)
|
||||
// Note: page-flipping is only supported with LFB modes. With banked modes,
|
||||
// we would still need to bank-switch through the same 64KB window to write
|
||||
// to the back page, so the performance benefit is minimal (just tear-free).
|
||||
// For simplicity, disable page-flipping in banked mode for now.
|
||||
if (!data->banked_mode && modedata->num_image_pages >= 1) {
|
||||
data->page_flip_available = true;
|
||||
data->current_page = 0;
|
||||
data->page_offset[0] = 0;
|
||||
data->page_offset[1] = (Uint32)modedata->pitch * (Uint32)modedata->h;
|
||||
|
||||
// Check that both pages fit within the mapped region.
|
||||
Uint32 page1_end = data->page_offset[1] + (Uint32)modedata->pitch * (Uint32)modedata->h;
|
||||
if (page1_end > data->mapping.size) {
|
||||
data->page_flip_available = false;
|
||||
data->page_offset[1] = 0;
|
||||
} else {
|
||||
// Also blank the second page
|
||||
SDL_memset((Uint8 *)DOS_PhysicalToLinear(data->mapping.address) + data->page_offset[1],
|
||||
'\0', (Uint32)modedata->pitch * (Uint32)modedata->h);
|
||||
|
||||
// Start display at page 0
|
||||
regs.x.ax = 0x4F07;
|
||||
regs.x.bx = 0x0000; // set display start, wait for retrace
|
||||
regs.x.cx = 0; // first pixel in scan line
|
||||
regs.x.dx = 0; // first scan line
|
||||
__dpmi_int(0x10, ®s);
|
||||
}
|
||||
} else {
|
||||
data->page_flip_available = false;
|
||||
data->current_page = 0;
|
||||
data->page_offset[0] = 0;
|
||||
data->page_offset[1] = 0;
|
||||
}
|
||||
|
||||
if (SDL_GetMouse()->internal != NULL) { // internal != NULL) == int 33h services available.
|
||||
regs.x.ax = 0x7; // set mouse min/max horizontal position.
|
||||
regs.x.cx = 0;
|
||||
regs.x.dx = (Uint16)(mode->w - 1);
|
||||
__dpmi_int(0x33, ®s);
|
||||
|
||||
regs.x.ax = 0x8; // set mouse min/max vertical position.
|
||||
regs.x.cx = 0;
|
||||
regs.x.dx = (Uint16)(mode->h - 1);
|
||||
__dpmi_int(0x33, ®s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SDL_VIDEO_DRIVER_DOSVESA
|
||||
50
src/video/dos/SDL_dosmodes.h
Normal file
50
src/video/dos/SDL_dosmodes.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_dosmodes_h_
|
||||
#define SDL_dosmodes_h_
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
extern bool DOSVESA_GetDisplayModes(SDL_VideoDevice *device, SDL_VideoDisplay *sdl_display);
|
||||
extern bool DOSVESA_SetDisplayMode(SDL_VideoDevice *device, SDL_VideoDisplay *sdl_display, SDL_DisplayMode *mode);
|
||||
extern bool DOSVESA_SupportsVESA(void);
|
||||
extern void DOSVESA_FreeVESAInfo(void);
|
||||
extern Uint32 DOSVESA_GetVESATotalMemory(void);
|
||||
extern const char *DOSVESA_GetGPUName(void);
|
||||
|
||||
// VGA DAC (Digital-to-Analog Converter) ports for palette programming
|
||||
#define VGA_DAC_PIXEL_MASK 0x3C6 // pixel mask register (read/write, VGA-only)
|
||||
#define VGA_DAC_WRITE_INDEX 0x3C8 // write index register (set starting color index)
|
||||
#define VGA_DAC_DATA 0x3C9 // data register (write R, G, B in sequence)
|
||||
|
||||
// VGA Input Status Register 1 (for vblank detection)
|
||||
#define VGA_STATUS_PORT 0x3DA
|
||||
#define VGA_STATUS_VBLANK 0x08 // bit 3: vertical retrace active
|
||||
|
||||
// VBE window attribute bits (WinAAttributes / WinBAttributes field)
|
||||
#define VBE_WINATTR_SUPPORTED 0x01 // bit 0: window is supported
|
||||
#define VBE_WINATTR_WRITABLE 0x04 // bit 2: window is writable
|
||||
|
||||
// Combination: a usable banked window must be both supported and writable
|
||||
#define VBE_WINATTR_USABLE (VBE_WINATTR_SUPPORTED | VBE_WINATTR_WRITABLE)
|
||||
|
||||
#endif // SDL_dosmodes_h_
|
||||
170
src/video/dos/SDL_dosmouse.c
Normal file
170
src/video/dos/SDL_dosmouse.c
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_DOSVESA
|
||||
|
||||
// https://stanislavs.org/helppc/int_33.html
|
||||
|
||||
#include "SDL_dosmouse.h"
|
||||
#include "SDL_dosvideo.h"
|
||||
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
#include "../../events/default_cursor.h"
|
||||
#include "../SDL_sysvideo.h"
|
||||
|
||||
// Create a cursor from a surface
|
||||
static SDL_Cursor *DOSVESA_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
|
||||
{
|
||||
SDL_CursorData *curdata;
|
||||
SDL_Cursor *cursor;
|
||||
|
||||
SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_assert(surface->pitch == surface->w * 4);
|
||||
|
||||
cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
|
||||
if (!cursor) {
|
||||
return NULL;
|
||||
}
|
||||
curdata = (SDL_CursorData *)SDL_calloc(1, sizeof(*curdata));
|
||||
if (!curdata) {
|
||||
SDL_free(cursor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
curdata->surface = SDL_DuplicateSurface(surface);
|
||||
if (!curdata->surface) {
|
||||
SDL_free(curdata);
|
||||
SDL_free(cursor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_SetSurfaceBlendMode(curdata->surface, SDL_BLENDMODE_BLEND);
|
||||
|
||||
curdata->hot_x = hot_x;
|
||||
curdata->hot_y = hot_y;
|
||||
curdata->w = surface->w;
|
||||
curdata->h = surface->h;
|
||||
|
||||
cursor->internal = curdata;
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
static SDL_Cursor *DOSVESA_CreateDefaultCursor(void)
|
||||
{
|
||||
return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY);
|
||||
}
|
||||
|
||||
// Show the specified cursor, or hide if cursor is NULL
|
||||
static bool DOSVESA_ShowCursor(SDL_Cursor *cursor)
|
||||
{
|
||||
return true; // we handle this elsewhere.
|
||||
}
|
||||
|
||||
// Free a window manager cursor
|
||||
static void DOSVESA_FreeCursor(SDL_Cursor *cursor)
|
||||
{
|
||||
SDL_CursorData *curdata;
|
||||
|
||||
if (cursor) {
|
||||
curdata = cursor->internal;
|
||||
|
||||
if (curdata) {
|
||||
SDL_DestroySurface(curdata->surface);
|
||||
SDL_DestroySurface(curdata->converted_surface);
|
||||
SDL_free(cursor->internal);
|
||||
}
|
||||
SDL_free(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DOSVESA_WarpMouseGlobal(float x, float y)
|
||||
{
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
|
||||
if (!mouse || !mouse->cur_cursor || !mouse->cur_cursor->internal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// warp mouse in the driver, so we get correct screen coordinates from it.
|
||||
__dpmi_regs regs;
|
||||
regs.x.ax = 0x4;
|
||||
regs.x.cx = (Uint16)x;
|
||||
regs.x.dx = (Uint16)y;
|
||||
__dpmi_int(0x33, ®s);
|
||||
|
||||
// Update internal mouse position.
|
||||
SDL_SendMouseMotion(0, mouse->focus, SDL_GLOBAL_MOUSE_ID, false, x, y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DOSVESA_WarpMouse(SDL_Window *window, float x, float y)
|
||||
{
|
||||
return DOSVESA_WarpMouseGlobal(x, y);
|
||||
}
|
||||
|
||||
// This is called when a mouse motion event occurs
|
||||
static bool DOSVESA_MoveCursor(SDL_Cursor *cursor)
|
||||
{
|
||||
return true; // we handle this elsewhere.
|
||||
}
|
||||
|
||||
void DOSVESA_InitMouse(SDL_VideoDevice *_this)
|
||||
{
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
mouse->internal = NULL;
|
||||
|
||||
__dpmi_regs regs;
|
||||
regs.x.ax = 0;
|
||||
__dpmi_int(0x33, ®s);
|
||||
if (regs.x.ax == 0) {
|
||||
return; // no mouse found, don't hook up cursor support, etc.
|
||||
}
|
||||
|
||||
mouse->internal = SDL_calloc(1, 1); // just something non-NULL (and safely freeable) to say "there's a mouse available."
|
||||
|
||||
// Query mouse sensitivity (mickeys per pixel) via INT 33h function 0x1B
|
||||
SDL_VideoData *data = _this->internal;
|
||||
regs.x.ax = 0x1B; // Get Mouse Sensitivity
|
||||
__dpmi_int(0x33, ®s);
|
||||
data->mickeys_per_hpixel = (regs.x.bx > 0) ? (float)regs.x.bx : 8.0f;
|
||||
data->mickeys_per_vpixel = (regs.x.cx > 0) ? (float)regs.x.cx : 16.0f;
|
||||
|
||||
mouse->CreateCursor = DOSVESA_CreateCursor;
|
||||
mouse->ShowCursor = DOSVESA_ShowCursor;
|
||||
mouse->MoveCursor = DOSVESA_MoveCursor;
|
||||
mouse->FreeCursor = DOSVESA_FreeCursor;
|
||||
mouse->WarpMouse = DOSVESA_WarpMouse;
|
||||
mouse->WarpMouseGlobal = DOSVESA_WarpMouseGlobal;
|
||||
|
||||
SDL_SetDefaultCursor(DOSVESA_CreateDefaultCursor());
|
||||
}
|
||||
|
||||
void DOSVESA_QuitMouse(SDL_VideoDevice *_this)
|
||||
{
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
SDL_free(mouse->internal);
|
||||
mouse->internal = NULL;
|
||||
}
|
||||
|
||||
#endif // SDL_VIDEO_DRIVER_DOSVESA
|
||||
39
src/video/dos/SDL_dosmouse.h
Normal file
39
src/video/dos/SDL_dosmouse.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_dosmouse_h_
|
||||
#define SDL_dosmouse_h_
|
||||
|
||||
#include "../SDL_sysvideo.h"
|
||||
|
||||
struct SDL_CursorData
|
||||
{
|
||||
SDL_Surface *surface;
|
||||
SDL_Surface *converted_surface;
|
||||
Uint32 converted_palette_version;
|
||||
int hot_x, hot_y;
|
||||
int w, h;
|
||||
};
|
||||
|
||||
extern void DOSVESA_InitMouse(SDL_VideoDevice *_this);
|
||||
extern void DOSVESA_QuitMouse(SDL_VideoDevice *_this);
|
||||
|
||||
#endif // SDL_dosmouse_h_
|
||||
356
src/video/dos/SDL_dosvideo.c
Normal file
356
src/video/dos/SDL_dosvideo.c
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_DOSVESA
|
||||
|
||||
// SDL internals
|
||||
#include "../../events/SDL_keyboard_c.h"
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
#include "../SDL_sysvideo.h"
|
||||
|
||||
// DOS declarations
|
||||
#include "SDL_dosevents_c.h"
|
||||
#include "SDL_dosframebuffer_c.h"
|
||||
#include "SDL_dosmodes.h"
|
||||
#include "SDL_dosmouse.h"
|
||||
#include "SDL_dosvideo.h"
|
||||
|
||||
// Apply a display mode for a window: set the hardware mode, store it as
|
||||
// the window's requested fullscreen mode, and update window dimensions.
|
||||
static void DOSVESA_ApplyModeForWindow(SDL_VideoDisplay *display, SDL_Window *window, SDL_DisplayMode *mode)
|
||||
{
|
||||
SDL_SetDisplayModeForDisplay(display, mode);
|
||||
|
||||
if (mode) {
|
||||
SDL_copyp(&window->requested_fullscreen_mode, mode);
|
||||
window->floating.w = window->windowed.w = window->w = mode->w;
|
||||
window->floating.h = window->windowed.h = window->h = mode->h;
|
||||
}
|
||||
}
|
||||
|
||||
static bool DOSVESA_CreateWindow(SDL_VideoDevice *device, SDL_Window *window, SDL_PropertiesID create_props)
|
||||
{
|
||||
// Allocate window internal data
|
||||
SDL_WindowData *wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
|
||||
if (!wdata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup driver data for this window
|
||||
window->internal = wdata;
|
||||
|
||||
// One window, it always has focus
|
||||
SDL_SetMouseFocus(window);
|
||||
SDL_SetKeyboardFocus(window);
|
||||
|
||||
{
|
||||
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
|
||||
if (!display) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_DisplayMode *mode = NULL;
|
||||
if (window->requested_fullscreen_mode.internal) {
|
||||
// App explicitly set a fullscreen mode.
|
||||
mode = &window->requested_fullscreen_mode;
|
||||
} else if (window->floating.w > 0 && window->floating.h > 0) {
|
||||
SDL_DisplayMode closest;
|
||||
if (SDL_GetClosestFullscreenDisplayMode(display->id, window->floating.w, window->floating.h, 0.0f, false, &closest)) {
|
||||
mode = &closest;
|
||||
}
|
||||
}
|
||||
if (!mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DOSVESA_ApplyModeForWindow(display, window, mode);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DOSVESA_SetWindowSize(SDL_VideoDevice *device, SDL_Window *window)
|
||||
{
|
||||
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_DisplayMode closest;
|
||||
SDL_DisplayMode *mode = NULL;
|
||||
if (SDL_GetClosestFullscreenDisplayMode(display->id, window->floating.w, window->floating.h, 0.0f, false, &closest)) {
|
||||
mode = &closest;
|
||||
}
|
||||
|
||||
DOSVESA_ApplyModeForWindow(display, window, mode);
|
||||
|
||||
// Invalidate the framebuffer so it gets recreated at the new size.
|
||||
window->surface_valid = false;
|
||||
}
|
||||
|
||||
// Critical for performance: this function must be implemented and as simple
|
||||
// as possible to avoid slowdowns during calls to SDL_UpdateWindowSurface().
|
||||
// A few pointer dereferences here can cost 10% of performance easily.
|
||||
static void DOSVESA_GetWindowSizeInPixels(SDL_VideoDevice *device, SDL_Window *window, int *w, int *h)
|
||||
{
|
||||
*w = window->w;
|
||||
*h = window->h;
|
||||
}
|
||||
|
||||
static void DOSVESA_DestroyWindow(SDL_VideoDevice *device, SDL_Window *window)
|
||||
{
|
||||
SDL_free(window->internal);
|
||||
window->internal = NULL;
|
||||
}
|
||||
|
||||
static bool DOSVESA_VideoInit(SDL_VideoDevice *device)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
|
||||
// Verify that the "fat DS" nearptr trick is active. Without it,
|
||||
// DOS_PhysicalToLinear() produces garbage pointers and we crash.
|
||||
// SDL_RunApp() enables this automatically; if the app defined
|
||||
// SDL_MAIN_HANDLED it must call __djgpp_nearptr_enable() itself.
|
||||
if (__djgpp_conventional_base == 0) {
|
||||
return SDL_SetError("DOSVESA: __djgpp_nearptr_enable() was not called. "
|
||||
"Did you define SDL_MAIN_HANDLED without enabling the fat DS trick?");
|
||||
}
|
||||
|
||||
// We are probably in text mode at startup, so we don't have a real "desktop mode" atm.
|
||||
// Pick something _super_ conservative for now.
|
||||
// We'll change to a real video mode after enumerating available modes below.
|
||||
SDL_DisplayMode mode;
|
||||
SDL_zero(mode);
|
||||
mode.format = SDL_PIXELFORMAT_RGB565;
|
||||
mode.w = 320;
|
||||
mode.h = 200;
|
||||
|
||||
SDL_VideoDisplay vdisplay;
|
||||
SDL_zero(vdisplay);
|
||||
SDL_memcpy(&vdisplay.desktop_mode, &mode, sizeof(mode));
|
||||
vdisplay.name = (char *)DOSVESA_GetGPUName();
|
||||
SDL_DisplayID display_id = SDL_AddVideoDisplay(&vdisplay, false);
|
||||
if (!display_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_zero(data->current_mode);
|
||||
|
||||
SDL_VideoDisplay *display = SDL_GetVideoDisplay(display_id);
|
||||
if (!display || !DOSVESA_GetDisplayModes(device, display)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pick a sensible default desktop mode. This determines the window
|
||||
// size for FULLSCREEN_ONLY. Target 640x480 as a safe default; apps
|
||||
// that want something else should call SDL_SetWindowFullscreenMode.
|
||||
{
|
||||
SDL_DisplayMode closest;
|
||||
if (SDL_GetClosestFullscreenDisplayMode(display_id, 640, 480, 0.0f, false, &closest)) {
|
||||
// Deep-copy the mode into desktop_mode. We need our own
|
||||
// internal allocation because SDL frees desktop_mode.internal
|
||||
// and fullscreen_modes[].internal independently.
|
||||
SDL_DisplayModeData *desktop_internal = (SDL_DisplayModeData *)SDL_malloc(sizeof(*desktop_internal));
|
||||
if (desktop_internal) {
|
||||
SDL_copyp(desktop_internal, (const SDL_DisplayModeData *)closest.internal);
|
||||
SDL_copyp(&display->desktop_mode, &closest);
|
||||
display->desktop_mode.internal = desktop_internal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current video mode so we can restore it on quit.
|
||||
// The VBE calls (0x4F03, 0x4F04) return 0x004F on success; on cards
|
||||
// without VBE support (or VBE < 1.2) they simply fail and we fall
|
||||
// back to restoring standard text mode 0x03.
|
||||
{
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F03; // VBE Get Current Mode
|
||||
__dpmi_int(0x10, ®s);
|
||||
if (regs.x.ax == 0x004F) {
|
||||
data->original_vbe_mode = regs.x.bx;
|
||||
} else {
|
||||
data->original_vbe_mode = 0x03; // assume text mode
|
||||
}
|
||||
|
||||
// Save VBE state via VBE function 0x4F04 so we can do a full restore later.
|
||||
// Step 1: query the required buffer size (subfunction 0x00).
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F04;
|
||||
regs.x.dx = 0x00; // subfunction 0: get state buffer size
|
||||
regs.x.cx = 0x0F; // save all state: hardware + BIOS data + DAC + SVGA
|
||||
__dpmi_int(0x10, ®s);
|
||||
if (regs.x.ax == 0x004F) {
|
||||
// regs.x.bx contains size in 64-byte blocks.
|
||||
Uint32 state_size = (Uint32)regs.x.bx * 64;
|
||||
_go32_dpmi_seginfo state_seginfo;
|
||||
void *state_buf = DOS_AllocateConventionalMemory(state_size, &state_seginfo);
|
||||
if (state_buf) {
|
||||
// Step 2: save state (subfunction 0x01) into conventional memory buffer.
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F04;
|
||||
regs.x.dx = 0x01; // subfunction 1: save state
|
||||
regs.x.cx = 0x0F; // all state
|
||||
regs.x.es = DOS_LinearToPhysical(state_buf) / 16;
|
||||
regs.x.bx = DOS_LinearToPhysical(state_buf) & 0xF;
|
||||
__dpmi_int(0x10, ®s);
|
||||
if (regs.x.ax == 0x004F) {
|
||||
// Copy state from conventional memory to our heap so we
|
||||
// can free the low-memory buffer now.
|
||||
data->vbe_state_buffer = SDL_malloc(state_size);
|
||||
if (data->vbe_state_buffer) {
|
||||
SDL_memcpy(data->vbe_state_buffer, state_buf, state_size);
|
||||
data->vbe_state_buffer_size = state_size;
|
||||
}
|
||||
}
|
||||
DOS_FreeConventionalMemory(&state_seginfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DOSVESA_InitMouse(device);
|
||||
DOSVESA_InitKeyboard(device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DOSVESA_VideoQuit(SDL_VideoDevice *device)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
|
||||
if (data->mapping.size) {
|
||||
__dpmi_free_physical_address_mapping(&data->mapping); // dump existing video mapping.
|
||||
SDL_zero(data->mapping);
|
||||
}
|
||||
|
||||
// Restore saved VBE state if available.
|
||||
if (data->vbe_state_buffer && data->vbe_state_buffer_size > 0) {
|
||||
_go32_dpmi_seginfo restore_seginfo;
|
||||
void *restore_buf = DOS_AllocateConventionalMemory(data->vbe_state_buffer_size, &restore_seginfo);
|
||||
if (restore_buf) {
|
||||
SDL_memcpy(restore_buf, data->vbe_state_buffer, data->vbe_state_buffer_size);
|
||||
__dpmi_regs regs;
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F04;
|
||||
regs.x.dx = 0x02; // subfunction 2: restore state
|
||||
regs.x.cx = 0x0F; // all state
|
||||
regs.x.es = DOS_LinearToPhysical(restore_buf) / 16;
|
||||
regs.x.bx = DOS_LinearToPhysical(restore_buf) & 0xF;
|
||||
__dpmi_int(0x10, ®s);
|
||||
DOS_FreeConventionalMemory(&restore_seginfo);
|
||||
}
|
||||
SDL_free(data->vbe_state_buffer);
|
||||
data->vbe_state_buffer = NULL;
|
||||
data->vbe_state_buffer_size = 0;
|
||||
}
|
||||
|
||||
// Restore the original video mode.
|
||||
{
|
||||
__dpmi_regs regs;
|
||||
bool restored = false;
|
||||
|
||||
// Try VBE mode restore first (only works on VBE 1.2+).
|
||||
if (data->original_vbe_mode != 0x03) {
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x4F02;
|
||||
regs.x.bx = data->original_vbe_mode;
|
||||
__dpmi_int(0x10, ®s);
|
||||
restored = (regs.x.ax == 0x004F);
|
||||
}
|
||||
|
||||
// Fall back to standard BIOS text mode (works on any VGA).
|
||||
if (!restored) {
|
||||
SDL_zero(regs);
|
||||
regs.x.ax = 0x0003;
|
||||
__dpmi_int(0x10, ®s);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_zero(data->current_mode);
|
||||
|
||||
DOSVESA_QuitMouse(device);
|
||||
DOSVESA_QuitKeyboard(device);
|
||||
}
|
||||
|
||||
static void DOSVESA_Destroy(SDL_VideoDevice *device)
|
||||
{
|
||||
SDL_VideoData *data = device->internal;
|
||||
SDL_free(data->vbe_state_buffer);
|
||||
SDL_free(device->internal);
|
||||
SDL_free(device);
|
||||
DOSVESA_FreeVESAInfo();
|
||||
}
|
||||
|
||||
static SDL_VideoDevice *DOSVESA_CreateDevice(void)
|
||||
{
|
||||
if (!DOSVESA_SupportsVESA()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_VideoDevice *device;
|
||||
SDL_VideoData *phdata;
|
||||
|
||||
// Initialize SDL_VideoDevice structure
|
||||
device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
|
||||
if (!device) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Initialize internal data
|
||||
phdata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData));
|
||||
if (!phdata) {
|
||||
SDL_free(device);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
device->internal = phdata;
|
||||
device->free = DOSVESA_Destroy;
|
||||
device->VideoInit = DOSVESA_VideoInit;
|
||||
device->VideoQuit = DOSVESA_VideoQuit;
|
||||
device->GetDisplayModes = DOSVESA_GetDisplayModes;
|
||||
device->SetDisplayMode = DOSVESA_SetDisplayMode;
|
||||
device->CreateSDLWindow = DOSVESA_CreateWindow;
|
||||
device->SetWindowSize = DOSVESA_SetWindowSize;
|
||||
device->DestroyWindow = DOSVESA_DestroyWindow;
|
||||
device->CreateWindowFramebuffer = DOSVESA_CreateWindowFramebuffer;
|
||||
device->SetWindowFramebufferVSync = DOSVESA_SetWindowFramebufferVSync;
|
||||
device->GetWindowFramebufferVSync = DOSVESA_GetWindowFramebufferVSync;
|
||||
device->UpdateWindowFramebuffer = DOSVESA_UpdateWindowFramebuffer;
|
||||
device->DestroyWindowFramebuffer = DOSVESA_DestroyWindowFramebuffer;
|
||||
device->GetWindowSizeInPixels = DOSVESA_GetWindowSizeInPixels;
|
||||
device->PumpEvents = DOSVESA_PumpEvents;
|
||||
device->device_caps = VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY;
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
VideoBootStrap DOSVESA_bootstrap = {
|
||||
"vesa",
|
||||
"DOS VESA Video Driver",
|
||||
DOSVESA_CreateDevice,
|
||||
NULL, // no ShowMessageBox implementation
|
||||
false
|
||||
};
|
||||
|
||||
#endif // SDL_VIDEO_DRIVER_DOSVESA
|
||||
89
src/video/dos/SDL_dosvideo.h
Normal file
89
src/video/dos/SDL_dosvideo.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SDL_dosvideo_h
|
||||
#define SDL_dosvideo_h
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../SDL_sysvideo.h"
|
||||
#include "SDL_internal.h"
|
||||
|
||||
struct SDL_DisplayModeData
|
||||
{
|
||||
// we can add more fields to this, if we want them, later.
|
||||
Uint16 mode_id;
|
||||
Uint16 attributes;
|
||||
Uint16 pitch;
|
||||
Uint16 w;
|
||||
Uint16 h;
|
||||
Uint8 num_planes;
|
||||
Uint8 bpp;
|
||||
Uint8 memory_model;
|
||||
Uint8 num_image_pages;
|
||||
Uint8 red_mask_size;
|
||||
Uint8 red_mask_pos;
|
||||
Uint8 green_mask_size;
|
||||
Uint8 green_mask_pos;
|
||||
Uint8 blue_mask_size;
|
||||
Uint8 blue_mask_pos;
|
||||
Uint32 physical_base_addr;
|
||||
|
||||
// VBE 1.2 banked framebuffer fields (used when LFB is not available)
|
||||
bool has_lfb; // true if linear framebuffer is available (VBE 2.0+)
|
||||
Uint16 win_granularity; // bank positioning granularity in KB
|
||||
Uint16 win_size; // window size in KB (typically 64)
|
||||
Uint16 win_a_segment; // real-mode segment of window A (typically 0xA000)
|
||||
Uint32 win_func_ptr; // real-mode far pointer to bank-switch function
|
||||
Uint8 win_a_attributes; // window A capabilities
|
||||
};
|
||||
|
||||
struct SDL_VideoData
|
||||
{
|
||||
__dpmi_meminfo mapping; // video memory mapping.
|
||||
SDL_DisplayMode current_mode;
|
||||
DOS_InterruptHook keyboard_interrupt_hook;
|
||||
Uint32 palette_version; // tracks SDL_Palette::version to detect changes
|
||||
Uint16 original_vbe_mode; // VBE mode number at startup
|
||||
void *vbe_state_buffer; // saved VBE state (from VBE 0x4F04)
|
||||
Uint32 vbe_state_buffer_size; // size of the state buffer
|
||||
|
||||
// Mouse sensitivity (mickeys per pixel), queried from INT 33h function 0x1B
|
||||
float mickeys_per_hpixel; // horizontal mickeys per pixel (default: 8)
|
||||
float mickeys_per_vpixel; // vertical mickeys per pixel (default: 16)
|
||||
|
||||
// Page-flipping (double-buffering) state
|
||||
int current_page; // 0 or 1: which page is currently displayed
|
||||
Uint32 page_offset[2]; // byte offset of each page within video memory
|
||||
bool page_flip_available; // true if mode supports double-buffering
|
||||
bool banked_mode; // true if current mode uses banked (not LFB) access
|
||||
};
|
||||
|
||||
struct SDL_DisplayData
|
||||
{
|
||||
int unused; // for now
|
||||
};
|
||||
|
||||
struct SDL_WindowData
|
||||
{
|
||||
int framebuffer_vsync;
|
||||
};
|
||||
|
||||
#endif // SDL_dosvideo_h
|
||||
@@ -88,12 +88,24 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.20)
|
||||
set(test_bin_dir "${test_bin_dir}$<$<BOOL:${is_multi_config}>:/$<CONFIG>>")
|
||||
endif()
|
||||
|
||||
if(DOS)
|
||||
set(NAME83_LONG "unifont-15.1.05.hex;unifont-15.1.05-license.txt;physaudiodev.png;logaudiodev.png;audiofile.png;soundboard.png;soundboard_levels.png;trashcan.png;msdf_font.png;msdf_font.csv;gamepad_front.png;gamepad_back.png;gamepad_face_abxy.png;gamepad_face_axby.png;gamepad_face_bayx.png;gamepad_face_sony.png;gamepad_battery.png;gamepad_battery_unknown.png;gamepad_battery_wired.png;gamepad_touchpad.png;gamepad_button.png;gamepad_button_small.png;gamepad_button_background.png;gamepad_axis.png;gamepad_axis_arrow.png;gamepad_wired.png;gamepad_wireless.png;sdl-test_round.png")
|
||||
set(DOS83_SHORT "UNIFONT.HEX;UNIFONTL.TXT;PHYSADEV.PNG;LOGADEV.PNG;AUDIOFIL.PNG;SNDBRD.PNG;SNDLVL.PNG;TRASHCAN.PNG;MSDFFONT.PNG;MSDFFONT.CSV;GP_FRONT.PNG;GP_BACK.PNG;GP_FABXY.PNG;GP_FAXBY.PNG;GP_FBAYX.PNG;GP_FSONY.PNG;GP_BATT.PNG;GP_BATTX.PNG;GP_BATTW.PNG;GP_TOUCH.PNG;GP_BTN.PNG;GP_BTNSM.PNG;GP_BTNBG.PNG;GP_AXIS.PNG;GP_AXARW.PNG;GP_WIRED.PNG;GP_WLESS.PNG;SDLROUND.PNG")
|
||||
endif()
|
||||
|
||||
set(RESOURCE_FILE_NAMES)
|
||||
set(RESOURCE_FILES_BINDIR)
|
||||
foreach(resource_file IN LISTS RESOURCE_FILES)
|
||||
get_filename_component(res_file_name ${resource_file} NAME)
|
||||
list(APPEND RESOURCE_FILE_NAMES "${res_file_name}")
|
||||
set(resource_file_bindir "${test_bin_dir}/${res_file_name}")
|
||||
set(output_name "${res_file_name}")
|
||||
if(DOS)
|
||||
list(FIND DOS83_LONG "${res_file_name}" _dos83_idx)
|
||||
if(NOT _dos83_idx EQUAL -1)
|
||||
list(GET DOS83_SHORT ${_dos83_idx} output_name)
|
||||
endif()
|
||||
endif()
|
||||
list(APPEND RESOURCE_FILE_NAMES "${output_name}")
|
||||
set(resource_file_bindir "${test_bin_dir}/${output_name}")
|
||||
set(depends_resource_file "${resource_file}")
|
||||
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
|
||||
set(depends_resource_file "${CMAKE_CURRENT_LIST_FILE}")
|
||||
@@ -128,7 +140,7 @@ if(HAVE_WAYLAND)
|
||||
endif()
|
||||
|
||||
function(add_sdl_test_executable TARGET)
|
||||
cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;THREADS;MAIN_CALLBACKS;NOTRACKMEM" "" "DEPENDS;DISABLE_THREADS_ARGS;NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;INSTALLED_ARGS;SOURCES" ${ARGN})
|
||||
cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;THREADS;MAIN_CALLBACKS;NOTRACKMEM" "NAME83" "DEPENDS;DISABLE_THREADS_ARGS;NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;INSTALLED_ARGS;SOURCES" ${ARGN})
|
||||
if(AST_UNPARSED_ARGUMENTS)
|
||||
message(FATAL_ERROR "Unknown argument(s): ${AST_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
@@ -227,6 +239,10 @@ function(add_sdl_test_executable TARGET)
|
||||
set_property(TARGET ${TARGET} PROPERTY "EXCLUDE_FROM_ALL" "1")
|
||||
set_propertY(TARGET ${TARGET} PROPERTY SDL_INSTALL "0")
|
||||
endif()
|
||||
elseif(DOS)
|
||||
if(AST_NAME83)
|
||||
set_property(TARGET ${TARGET} PROPERTY OUTPUT_NAME "${AST_NAME83}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(OPENGL_FOUND)
|
||||
@@ -321,34 +337,34 @@ else()
|
||||
message(STATUS "Can't find ffmpeg 5.1.3 or newer, skipping testffmpeg")
|
||||
endif()
|
||||
|
||||
add_sdl_test_executable(checkkeys SOURCES checkkeys.c)
|
||||
add_sdl_test_executable(checkkeys SOURCES checkkeys.c NAME83 chkkeys)
|
||||
add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS MAIN_CALLBACKS SOURCES loopwave.c)
|
||||
add_sdl_test_executable(testsurround SOURCES testsurround.c)
|
||||
add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c)
|
||||
add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c)
|
||||
add_sdl_test_executable(testaudiostreamdynamicresample NEEDS_RESOURCES TESTUTILS SOURCES testaudiostreamdynamicresample.c)
|
||||
add_sdl_test_executable(testsurround SOURCES testsurround.c NAME83 surround)
|
||||
add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c NAME83 resample)
|
||||
add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c NAME83 audioinf)
|
||||
add_sdl_test_executable(testaudiostreamdynamicresample NEEDS_RESOURCES TESTUTILS SOURCES testaudiostreamdynamicresample.c NAME83 audynres)
|
||||
|
||||
file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c)
|
||||
add_sdl_test_executable(testautomation NONINTERACTIVE NONINTERACTIVE_TIMEOUT 120 NEEDS_RESOURCES BUILD_DEPENDENT SOURCES ${TESTAUTOMATION_SOURCE_FILES})
|
||||
add_sdl_test_executable(testautomation NONINTERACTIVE NONINTERACTIVE_TIMEOUT 120 NEEDS_RESOURCES BUILD_DEPENDENT SOURCES ${TESTAUTOMATION_SOURCE_FILES} NAME83 automat)
|
||||
if(EMSCRIPTEN)
|
||||
target_link_options(testautomation PRIVATE -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1gb)
|
||||
endif()
|
||||
add_sdl_test_executable(testmultiaudio NEEDS_RESOURCES TESTUTILS SOURCES testmultiaudio.c)
|
||||
add_sdl_test_executable(testaudiohotplug NEEDS_RESOURCES TESTUTILS SOURCES testaudiohotplug.c)
|
||||
add_sdl_test_executable(testaudiorecording MAIN_CALLBACKS SOURCES testaudiorecording.c)
|
||||
add_sdl_test_executable(testatomic NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" SOURCES testatomic.c)
|
||||
add_sdl_test_executable(testintersections SOURCES testintersections.c)
|
||||
add_sdl_test_executable(testrelative SOURCES testrelative.c)
|
||||
add_sdl_test_executable(testhittesting SOURCES testhittesting.c)
|
||||
add_sdl_test_executable(testdraw SOURCES testdraw.c)
|
||||
add_sdl_test_executable(testdrawchessboard SOURCES testdrawchessboard.c)
|
||||
add_sdl_test_executable(testdropfile MAIN_CALLBACKS SOURCES testdropfile.c)
|
||||
add_sdl_test_executable(testerror NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" SOURCES testerror.c)
|
||||
add_sdl_test_executable(testsymbols NONINTERACTIVE NOTRACKMEM NONINTERACTIVE_ARGS 0 10 20 40 80 160 320 640 SOURCES testsymbols.c)
|
||||
add_sdl_test_executable(testmultiaudio NEEDS_RESOURCES TESTUTILS SOURCES testmultiaudio.c NAME83 multaudi)
|
||||
add_sdl_test_executable(testaudiohotplug NEEDS_RESOURCES TESTUTILS SOURCES testaudiohotplug.c NAME83 audhotpl)
|
||||
add_sdl_test_executable(testaudiorecording MAIN_CALLBACKS SOURCES testaudiorecording.c NAME83 aurecord)
|
||||
add_sdl_test_executable(testatomic NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" SOURCES testatomic.c NAME83 atomic)
|
||||
add_sdl_test_executable(testintersections SOURCES testintersections.c NAME83 intersec)
|
||||
add_sdl_test_executable(testrelative SOURCES testrelative.c NAME83 relative)
|
||||
add_sdl_test_executable(testhittesting SOURCES testhittesting.c NAME83 hittest)
|
||||
add_sdl_test_executable(testdraw SOURCES testdraw.c NAME83 draw)
|
||||
add_sdl_test_executable(testdrawchessboard SOURCES testdrawchessboard.c NAME83 drawches)
|
||||
add_sdl_test_executable(testdropfile MAIN_CALLBACKS SOURCES testdropfile.c NAME83 dropfile)
|
||||
add_sdl_test_executable(testerror NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" SOURCES testerror.c NAME83 error)
|
||||
add_sdl_test_executable(testsymbols NONINTERACTIVE NOTRACKMEM NONINTERACTIVE_ARGS 0 10 20 40 80 160 320 640 SOURCES testsymbols.c NAME83 symbols)
|
||||
|
||||
set(build_options_dependent_tests )
|
||||
|
||||
add_sdl_test_executable(testevdev BUILD_DEPENDENT NONINTERACTIVE SOURCES testevdev.c)
|
||||
add_sdl_test_executable(testevdev BUILD_DEPENDENT NONINTERACTIVE SOURCES testevdev.c NAME83 evdev)
|
||||
|
||||
if(MACOS)
|
||||
add_sdl_test_executable(testnative BUILD_DEPENDENT NEEDS_RESOURCES TESTUTILS
|
||||
@@ -375,20 +391,20 @@ elseif(HAVE_X11 OR HAVE_WAYLAND)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
add_sdl_test_executable(testasyncio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testasyncio.c)
|
||||
add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c)
|
||||
add_sdl_test_executable(testcolorspace SOURCES testcolorspace.c)
|
||||
add_sdl_test_executable(testasyncio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testasyncio.c NAME83 asyncio)
|
||||
add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c NAME83 audio)
|
||||
add_sdl_test_executable(testcolorspace SOURCES testcolorspace.c NAME83 colorspc)
|
||||
add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c)
|
||||
add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers} DEPENDS generate-gamepad_image_headers)
|
||||
add_sdl_test_executable(testdlopennote TESTUTILS SOURCES testdlopennote.c)
|
||||
add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c)
|
||||
add_sdl_test_executable(testgl SOURCES testgl.c)
|
||||
add_sdl_test_executable(testgles SOURCES testgles.c)
|
||||
add_sdl_test_executable(testgpu_simple_clear SOURCES testgpu_simple_clear.c)
|
||||
add_sdl_test_executable(testgpu_spinning_cube SOURCES testgpu_spinning_cube.c ${icon_png_header} DEPENDS generate-icon_png_header)
|
||||
add_sdl_test_executable(testgpurender_effects MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testgpurender_effects.c)
|
||||
add_sdl_test_executable(testgpurender_msdf MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testgpurender_msdf.c)
|
||||
add_sdl_test_executable(testgpu_spinning_cube_xr SOURCES testgpu_spinning_cube_xr.c)
|
||||
add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers} DEPENDS generate-gamepad_image_headers NAME83 control)
|
||||
add_sdl_test_executable(testdlopennote TESTUTILS SOURCES testdlopennote.c NAME83 dlnote)
|
||||
add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c NAME83 geometry)
|
||||
add_sdl_test_executable(testgl SOURCES testgl.c NAME83 gl)
|
||||
add_sdl_test_executable(testgles SOURCES testgles.c NAME83 tstgles NAME83 gles)
|
||||
add_sdl_test_executable(testgpu_simple_clear SOURCES testgpu_simple_clear.c NAME83 gpuclear)
|
||||
add_sdl_test_executable(testgpu_spinning_cube SOURCES testgpu_spinning_cube.c ${icon_png_header} DEPENDS generate-icon_png_header NAME83 gpucube)
|
||||
add_sdl_test_executable(testgpurender_effects MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testgpurender_effects.c NAME83 gpufx)
|
||||
add_sdl_test_executable(testgpurender_msdf MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testgpurender_msdf.c NAME83 gpumsdf)
|
||||
add_sdl_test_executable(testgpu_spinning_cube_xr SOURCES testgpu_spinning_cube_xr.c NAME83 gpucubxr)
|
||||
|
||||
if(ANDROID)
|
||||
target_link_libraries(testgles PRIVATE GLESv1_CM)
|
||||
@@ -396,70 +412,70 @@ elseif(IOS OR TVOS)
|
||||
find_library(GLES_LIB OpenGLES REQUIRED)
|
||||
target_link_libraries(testgles PRIVATE "${GLES_LIB}")
|
||||
endif()
|
||||
add_sdl_test_executable(testgles2 SOURCES testgles2.c)
|
||||
add_sdl_test_executable(testhaptic SOURCES testhaptic.c)
|
||||
add_sdl_test_executable(testhotplug SOURCES testhotplug.c)
|
||||
add_sdl_test_executable(testpen SOURCES testpen.c)
|
||||
add_sdl_test_executable(testrumble SOURCES testrumble.c)
|
||||
add_sdl_test_executable(testthread NONINTERACTIVE THREADS NONINTERACTIVE_TIMEOUT 40 SOURCES testthread.c)
|
||||
add_sdl_test_executable(testiconv NEEDS_RESOURCES TESTUTILS SOURCES testiconv.c)
|
||||
add_sdl_test_executable(testime NEEDS_RESOURCES TESTUTILS SOURCES testime.c)
|
||||
add_sdl_test_executable(testkeys SOURCES testkeys.c)
|
||||
add_sdl_test_executable(testloadso SOURCES testloadso.c)
|
||||
add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c)
|
||||
add_sdl_test_executable(testlock SOURCES testlock.c)
|
||||
add_sdl_test_executable(testrwlock SOURCES testrwlock.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 20)
|
||||
add_sdl_test_executable(testmouse SOURCES testmouse.c)
|
||||
add_sdl_test_executable(testgles2 SOURCES testgles2.c NAME83 gles2)
|
||||
add_sdl_test_executable(testhaptic SOURCES testhaptic.c NAME83 haptic)
|
||||
add_sdl_test_executable(testhotplug SOURCES testhotplug.c NAME83 hotplug)
|
||||
add_sdl_test_executable(testpen SOURCES testpen.c NAME83 pen)
|
||||
add_sdl_test_executable(testrumble SOURCES testrumble.c NAME83 rumble)
|
||||
add_sdl_test_executable(testthread NONINTERACTIVE THREADS NONINTERACTIVE_TIMEOUT 40 SOURCES testthread.c NAME83 thread)
|
||||
add_sdl_test_executable(testiconv NEEDS_RESOURCES TESTUTILS SOURCES testiconv.c NAME83 iconv)
|
||||
add_sdl_test_executable(testime NEEDS_RESOURCES TESTUTILS SOURCES testime.c NAME83 ime)
|
||||
add_sdl_test_executable(testkeys SOURCES testkeys.c NAME83 keys)
|
||||
add_sdl_test_executable(testloadso SOURCES testloadso.c NAME83 loadso)
|
||||
add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c NAME83 locale)
|
||||
add_sdl_test_executable(testlock SOURCES testlock.c NAME83 lock)
|
||||
add_sdl_test_executable(testrwlock SOURCES testrwlock.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 20 NAME83 rwlock)
|
||||
add_sdl_test_executable(testmouse SOURCES testmouse.c NAME83 mouse)
|
||||
|
||||
add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c)
|
||||
add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c)
|
||||
add_sdl_test_executable(testpower NONINTERACTIVE SOURCES testpower.c)
|
||||
add_sdl_test_executable(testfilesystem NONINTERACTIVE SOURCES testfilesystem.c)
|
||||
add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c NAME83 overlay)
|
||||
add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c NAME83 platform)
|
||||
add_sdl_test_executable(testpower NONINTERACTIVE SOURCES testpower.c NAME83 power)
|
||||
add_sdl_test_executable(testfilesystem NONINTERACTIVE SOURCES testfilesystem.c NAME83 filesyst)
|
||||
if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
add_sdl_test_executable(pretest SOURCES pretest.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 60)
|
||||
endif()
|
||||
add_sdl_test_executable(testrendertarget NEEDS_RESOURCES TESTUTILS SOURCES testrendertarget.c)
|
||||
add_sdl_test_executable(testrotate SOURCES testrotate.c)
|
||||
add_sdl_test_executable(testscale NEEDS_RESOURCES TESTUTILS SOURCES testscale.c)
|
||||
add_sdl_test_executable(testsem NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" NONINTERACTIVE_ARGS 10 NONINTERACTIVE_TIMEOUT 30 SOURCES testsem.c)
|
||||
add_sdl_test_executable(testsensor SOURCES testsensor.c)
|
||||
add_sdl_test_executable(testshader NEEDS_RESOURCES TESTUTILS SOURCES testshader.c)
|
||||
add_sdl_test_executable(testrendertarget NEEDS_RESOURCES TESTUTILS SOURCES testrendertarget.c NAME83 rendrtgt)
|
||||
add_sdl_test_executable(testrotate SOURCES testrotate.c NAME83 rotate)
|
||||
add_sdl_test_executable(testscale NEEDS_RESOURCES TESTUTILS SOURCES testscale.c NAME83 scale)
|
||||
add_sdl_test_executable(testsem NONINTERACTIVE DISABLE_THREADS_ARGS "--no-threads" NONINTERACTIVE_ARGS 10 NONINTERACTIVE_TIMEOUT 30 SOURCES testsem.c NAME83 sem)
|
||||
add_sdl_test_executable(testsensor SOURCES testsensor.c NAME83 sensor)
|
||||
add_sdl_test_executable(testshader NEEDS_RESOURCES TESTUTILS SOURCES testshader.c NAME83 shader)
|
||||
if(EMSCRIPTEN)
|
||||
target_link_options(testshader PRIVATE "-sLEGACY_GL_EMULATION")
|
||||
endif()
|
||||
add_sdl_test_executable(testshape NEEDS_RESOURCES SOURCES testshape.c ${glass_png_header} DEPENDS generate-glass_png_header)
|
||||
add_sdl_test_executable(testsoftwaretransparent SOURCES testsoftwaretransparent.c)
|
||||
add_sdl_test_executable(testsprite MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c)
|
||||
add_sdl_test_executable(testspriteminimal SOURCES testspriteminimal.c ${icon_png_header} DEPENDS generate-icon_png_header)
|
||||
add_sdl_test_executable(testspritesurface SOURCES testspritesurface.c ${icon_png_header} DEPENDS generate-icon_png_header)
|
||||
add_sdl_test_executable(testpalette SOURCES testpalette.c)
|
||||
add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c)
|
||||
add_sdl_test_executable(testurl SOURCES testurl.c)
|
||||
add_sdl_test_executable(testver NONINTERACTIVE NOTRACKMEM SOURCES testver.c)
|
||||
add_sdl_test_executable(testshape NEEDS_RESOURCES SOURCES testshape.c ${glass_png_header} DEPENDS generate-glass_png_header NAME83 shape)
|
||||
add_sdl_test_executable(testsoftwaretransparent SOURCES testsoftwaretransparent.c NAME83 swtransp)
|
||||
add_sdl_test_executable(testsprite MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c NAME83 sprite)
|
||||
add_sdl_test_executable(testspriteminimal SOURCES testspriteminimal.c ${icon_png_header} DEPENDS generate-icon_png_header NAME83 spritmin)
|
||||
add_sdl_test_executable(testspritesurface SOURCES testspritesurface.c ${icon_png_header} DEPENDS generate-icon_png_header NAME83 spritsrf)
|
||||
add_sdl_test_executable(testpalette SOURCES testpalette.c NAME83 palette)
|
||||
add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c NAME83 timer)
|
||||
add_sdl_test_executable(testurl SOURCES testurl.c NAME83 url)
|
||||
add_sdl_test_executable(testver NONINTERACTIVE NOTRACKMEM SOURCES testver.c NAME83 versdl)
|
||||
set_property(TARGET testver PROPERTY C_STANDARD 90)
|
||||
add_sdl_test_executable(testcamera MAIN_CALLBACKS SOURCES testcamera.c)
|
||||
add_sdl_test_executable(testclipboard MAIN_CALLBACKS SOURCES testclipboard.c ${icon_png_header} DEPENDS generate-icon_png_header)
|
||||
add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c)
|
||||
add_sdl_test_executable(testwm SOURCES testwm.c)
|
||||
add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c)
|
||||
add_sdl_test_executable(torturethread NONINTERACTIVE THREADS NONINTERACTIVE_TIMEOUT 30 SOURCES torturethread.c)
|
||||
add_sdl_test_executable(testrendercopyex NEEDS_RESOURCES TESTUTILS SOURCES testrendercopyex.c)
|
||||
add_sdl_test_executable(testmessage SOURCES testmessage.c)
|
||||
add_sdl_test_executable(testdisplayinfo SOURCES testdisplayinfo.c)
|
||||
add_sdl_test_executable(testqsort NONINTERACTIVE SOURCES testqsort.c)
|
||||
add_sdl_test_executable(testcamera MAIN_CALLBACKS SOURCES testcamera.c NAME83 camera)
|
||||
add_sdl_test_executable(testclipboard MAIN_CALLBACKS SOURCES testclipboard.c ${icon_png_header} DEPENDS generate-icon_png_header NAME83 clipbrd)
|
||||
add_sdl_test_executable(testviewport NEEDS_RESOURCES TESTUTILS SOURCES testviewport.c NAME83 viewport)
|
||||
add_sdl_test_executable(testwm SOURCES testwm.c NAME83 wm)
|
||||
add_sdl_test_executable(testyuv NONINTERACTIVE NONINTERACTIVE_ARGS "--automated" NEEDS_RESOURCES TESTUTILS SOURCES testyuv.c testyuv_cvt.c NAME83 yuv)
|
||||
add_sdl_test_executable(torturethread NONINTERACTIVE THREADS NONINTERACTIVE_TIMEOUT 30 SOURCES torturethread.c NAME83 tortthrd)
|
||||
add_sdl_test_executable(testrendercopyex NEEDS_RESOURCES TESTUTILS SOURCES testrendercopyex.c NAME83 rndcopex)
|
||||
add_sdl_test_executable(testmessage SOURCES testmessage.c NAME83 message)
|
||||
add_sdl_test_executable(testdisplayinfo SOURCES testdisplayinfo.c NAME83 dispinfo)
|
||||
add_sdl_test_executable(testqsort NONINTERACTIVE SOURCES testqsort.c NAME83 qsort)
|
||||
if(EMSCRIPTEN)
|
||||
target_link_options(testqsort PRIVATE -sALLOW_MEMORY_GROWTH)
|
||||
endif()
|
||||
add_sdl_test_executable(testbounds NONINTERACTIVE SOURCES testbounds.c)
|
||||
add_sdl_test_executable(testcustomcursor SOURCES testcustomcursor.c)
|
||||
add_sdl_test_executable(testvulkan SOURCES testvulkan.c)
|
||||
add_sdl_test_executable(testoffscreen SOURCES testoffscreen.c)
|
||||
add_sdl_test_executable(testpopup SOURCES testpopup.c)
|
||||
add_sdl_test_executable(testdialog SOURCES testdialog.c)
|
||||
add_sdl_test_executable(testtime SOURCES testtime.c)
|
||||
add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c)
|
||||
add_sdl_test_executable(testmodal SOURCES testmodal.c)
|
||||
add_sdl_test_executable(testtray NEEDS_RESOURCES TESTUTILS SOURCES testtray.c)
|
||||
add_sdl_test_executable(testbounds NONINTERACTIVE SOURCES testbounds.c NAME83 bounds)
|
||||
add_sdl_test_executable(testcustomcursor SOURCES testcustomcursor.c NAME83 custcurs)
|
||||
add_sdl_test_executable(testvulkan SOURCES testvulkan.c NAME83 vulkan)
|
||||
add_sdl_test_executable(testoffscreen SOURCES testoffscreen.c NAME83 offscrn)
|
||||
add_sdl_test_executable(testpopup SOURCES testpopup.c NAME83 popup)
|
||||
add_sdl_test_executable(testdialog SOURCES testdialog.c NAME83 dialog)
|
||||
add_sdl_test_executable(testtime SOURCES testtime.c NAME83 timesdl)
|
||||
add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c NAME83 manymous)
|
||||
add_sdl_test_executable(testmodal SOURCES testmodal.c NAME83 modal)
|
||||
add_sdl_test_executable(testtray NEEDS_RESOURCES TESTUTILS SOURCES testtray.c NAME83 tray)
|
||||
|
||||
|
||||
add_sdl_test_executable(testprocess
|
||||
@@ -467,8 +483,9 @@ add_sdl_test_executable(testprocess
|
||||
NONINTERACTIVE_ARGS $<TARGET_FILE:childprocess>
|
||||
INSTALLED_ARGS "${CMAKE_INSTALL_FULL_LIBEXECDIR}/installed-tests/SDL3/childprocess${CMAKE_EXECUTABLE_SUFFIX}"
|
||||
SOURCES testprocess.c
|
||||
NAME83 process
|
||||
)
|
||||
add_sdl_test_executable(childprocess SOURCES childprocess.c)
|
||||
add_sdl_test_executable(childprocess SOURCES childprocess.c NAME83 chldproc)
|
||||
add_dependencies(testprocess childprocess)
|
||||
|
||||
get_property(SDL_TEST_EXECUTABLES DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY SDL_TEST_EXECUTABLES)
|
||||
|
||||
@@ -166,7 +166,11 @@ int main(int argc, char *argv[])
|
||||
RWOP_ERR_QUIT(iostrm);
|
||||
}
|
||||
if (0 != SDL_ReadIO(iostrm, test_buf, 1)) {
|
||||
#ifdef __DJGPP__
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "DJGPP allowed read on write-only file");
|
||||
#else
|
||||
RWOP_ERR_QUIT(iostrm); /* we are in write only mode */
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_CloseIO(iostrm);
|
||||
@@ -203,7 +207,11 @@ int main(int argc, char *argv[])
|
||||
RWOP_ERR_QUIT(iostrm);
|
||||
}
|
||||
if (0 != SDL_WriteIO(iostrm, test_buf, 1)) {
|
||||
#ifdef __DJGPP__
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "DJGPP allowed write on read-only file");
|
||||
#else
|
||||
RWOP_ERR_QUIT(iostrm); /* readonly mode */
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_CloseIO(iostrm);
|
||||
|
||||
@@ -25,7 +25,11 @@
|
||||
#include <SDL3/SDL_test_common.h>
|
||||
#include "testutils.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
#define DEFAULT_FONT "UNIFONT.HEX"
|
||||
#else
|
||||
#define DEFAULT_FONT "unifont-15.1.05.hex"
|
||||
#endif
|
||||
#define MAX_TEXT_LENGTH 256
|
||||
|
||||
#define WINDOW_WIDTH 640
|
||||
|
||||
@@ -321,9 +321,13 @@ int main(int argc, char **argv)
|
||||
TestOverheadUncontended();
|
||||
|
||||
if (enable_threads) {
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
SDL_Log("Skipping contended overhead tests (too slow for cooperative threading)");
|
||||
#else
|
||||
TestOverheadContended(false);
|
||||
|
||||
TestOverheadContended(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
@@ -13,6 +13,55 @@
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
static const struct
|
||||
{
|
||||
const char *longname;
|
||||
const char *shortname;
|
||||
} names83_map[] = {
|
||||
{ "unifont-15.1.05.hex", "UNIFONT.HEX" },
|
||||
{ "unifont-15.1.05-license.txt", "UNIFONTL.TXT" },
|
||||
{ "physaudiodev.png", "PHYSADEV.PNG" },
|
||||
{ "logaudiodev.png", "LOGADEV.PNG" },
|
||||
{ "audiofile.png", "AUDIOFIL.PNG" },
|
||||
{ "soundboard.png", "SNDBRD.PNG" },
|
||||
{ "soundboard_levels.png", "SNDLVL.PNG" },
|
||||
{ "trashcan.png", "TRASHCAN.PNG" },
|
||||
{ "msdf_font.png", "MSDFFONT.PNG" },
|
||||
{ "msdf_font.csv", "MSDFFONT.CSV" },
|
||||
{ "gamepad_front.png", "GP_FRONT.PNG" },
|
||||
{ "gamepad_back.png", "GP_BACK.PNG" },
|
||||
{ "gamepad_face_abxy.png", "GP_FABXY.PNG" },
|
||||
{ "gamepad_face_axby.png", "GP_FAXBY.PNG" },
|
||||
{ "gamepad_face_bayx.png", "GP_FBAYX.PNG" },
|
||||
{ "gamepad_face_sony.png", "GP_FSONY.PNG" },
|
||||
{ "gamepad_battery.png", "GP_BATT.PNG" },
|
||||
{ "gamepad_battery_unknown.png", "GP_BATTX.PNG" },
|
||||
{ "gamepad_battery_wired.png", "GP_BATTW.PNG" },
|
||||
{ "gamepad_touchpad.png", "GP_TOUCH.PNG" },
|
||||
{ "gamepad_button.png", "GP_BTN.PNG" },
|
||||
{ "gamepad_button_small.png", "GP_BTNSM.PNG" },
|
||||
{ "gamepad_button_background.png", "GP_BTNBG.PNG" },
|
||||
{ "gamepad_axis.png", "GP_AXIS.PNG" },
|
||||
{ "gamepad_axis_arrow.png", "GP_AXARW.PNG" },
|
||||
{ "gamepad_wired.png", "GP_WIRED.PNG" },
|
||||
{ "gamepad_wireless.png", "GP_WLESS.PNG" },
|
||||
{ "sdl-test_round.png", "SDLROUND.PNG" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const char *Map83Filename(const char *file)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; names83_map[i].longname; i++) {
|
||||
if (SDL_strcasecmp(file, names83_map[i].longname) == 0) {
|
||||
return names83_map[i].shortname;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Return the absolute path to def in the SDL_GetBasePath() if possible, or
|
||||
* the relative path to def on platforms that don't have a working
|
||||
@@ -23,6 +72,9 @@
|
||||
char *GetNearbyFilename(const char *file)
|
||||
{
|
||||
const char *base = SDL_GetBasePath();
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
file = Map83Filename(file);
|
||||
#endif
|
||||
char *path;
|
||||
|
||||
if (base) {
|
||||
@@ -59,6 +111,9 @@ char *GetResourceFilename(const char *user_specified, const char *def)
|
||||
if (user_specified) {
|
||||
return SDL_strdup(user_specified);
|
||||
}
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
def = Map83Filename(def);
|
||||
#endif
|
||||
return GetNearbyFilename(def);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,9 @@ draw_modes_menu(SDL_Window *window, SDL_Renderer *renderer, SDL_FRect viewport)
|
||||
for (j = 0; modes[j]; ++j) {
|
||||
SDL_FRect cell_rect;
|
||||
const SDL_DisplayMode *mode = modes[j];
|
||||
if (mode->format == SDL_PIXELFORMAT_INDEX8) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)SDL_snprintf(text, sizeof(text), "%s mode %d: %dx%d@%gx %gHz",
|
||||
SDL_GetDisplayName(display),
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <SDL3/SDL_test.h>
|
||||
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
#define NUMTHREADS 3 /* DOS cooperative scheduler has limited thread slots */
|
||||
#else
|
||||
#define NUMTHREADS 10
|
||||
#endif
|
||||
|
||||
static SDL_AtomicInt time_for_threads_to_die[NUMTHREADS];
|
||||
|
||||
@@ -63,7 +67,9 @@ ThreadFunc(void *data)
|
||||
|
||||
SDL_Log("Thread '%d' waiting for signal", tid);
|
||||
while (SDL_GetAtomicInt(&time_for_threads_to_die[tid]) != 1) {
|
||||
; /* do nothing */
|
||||
#ifdef SDL_PLATFORM_DOS
|
||||
SDL_Delay(0); /* Yield for cooperative threading */
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_Log("Thread '%d' sending signals to subthreads", tid);
|
||||
|
||||
Reference in New Issue
Block a user