diff --git a/.travis.yml b/.travis.yml index 6962cbaff..39b3ee086 100644 --- a/.travis.yml +++ b/.travis.yml @@ -93,16 +93,15 @@ matrix: - os: linux compiler: clang env: - - CLANG_VERSION=6.0 + - CLANG_VERSION=7 - CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=MinSizeRel -DDYN_OPENAL=NO -DDYN_SNDFILE=NO -DDYN_MPG123=NO -DDYN_FLUIDSYNTH=NO" addons: apt: sources: - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-6.0 + - llvm-toolchain-trusty-7 packages: - - clang-6.0 - - libstdc++-5-dev + - clang-7 - libsdl2-dev - libgme-dev - libopenal-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index eed9d736b..3cc988c73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ function( assort_pk3_source_folder FOLDER_NAME PK3_DIR ) assort_pk3_source_folder( ${FOLDER_NAME}\\${DIRNAME} ${PK3_SRC} ) endif() # Assign IDE group for current top-level source files - source_group(${FOLDER_NAME} FILES ${PK3_SRCS}) + source_group(${FOLDER_NAME} FILES ${PK3_SRCS}) endforeach() endfunction() @@ -78,7 +78,7 @@ function( add_pk3 PK3_NAME PK3_DIR ) DEPENDS zipdir ) endif() # Create a list of source files for this PK3, for use in the IDE - # Phase 1: Create a list of all source files for this PK3 archive, except + # Phase 1: Create a list of all source files for this PK3 archive, except # for a couple of strife image file names that confuse CMake. file(GLOB_RECURSE PK3_SRCS ${PK3_DIR}/*) # Exclude from the source list some gzdoom .png files with brackets in the @@ -181,7 +181,7 @@ if( MSVC ) # Function-level linking # Disable run-time type information set( ALL_C_FLAGS "/GF /Gy /GR-" ) - + # Use SSE 2 as minimum always as the true color drawers needs it for __vectorcall #set( ALL_C_FLAGS "${ALL_C_FLAGS} /arch:SSE2") # This is already the default @@ -200,7 +200,7 @@ if( MSVC ) # else() # set( ALL_C_FLAGS "${ALL_C_FLAGS} /arch:SSE2") # endif() - + # Avoid CRT DLL dependancies in release builds, optionally generate assembly output for checking crash locations. option( ZDOOM_GENERATE_ASM "Generate assembly output." OFF ) if( ZDOOM_GENERATE_ASM ) @@ -209,7 +209,7 @@ if( MSVC ) set( REL_C_FLAGS "/MT /Oy /Oi /GS-" ) endif() - + # Debug allocations in debug builds set( DEB_C_FLAGS "/D _CRTDBG_MAP_ALLOC /MTd" ) @@ -217,7 +217,7 @@ if( MSVC ) if( MSVC_VERSION GREATER 1399 ) set( ALL_C_FLAGS "${ALL_C_FLAGS} /wd4996" ) endif() - + # The CMake configurations set /GR and /MD by default, which conflict with our settings. string(REPLACE "/MD " " " CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} ) string(REPLACE "/MD " " " CMAKE_CXX_FLAGS_MINSIZEREL ${CMAKE_CXX_FLAGS_MINSIZEREL} ) @@ -234,7 +234,7 @@ else() set( REL_C_FLAGS "" ) set( DEB_C_FLAGS "" ) - + if( APPLE ) if( CMAKE_CXX_COMPILER_ID STREQUAL "Clang" ) # With standard Apple tools -stdlib=libc++ needs to be specified in order to get @@ -328,6 +328,9 @@ if( GME_FOUND AND NOT FORCE_INTERNAL_GME ) message( STATUS "Using system gme library, includes found at ${GME_INCLUDE_DIR}" ) else() message( STATUS "Using internal gme library" ) + # Use MAME as it's balanced emulator: well-accurate, but doesn't eats lot of CPU + # Nuked OPN2 is very accurate emulator, but it eats too much CPU for the workflow + set( GME_YM2612_EMU "MAME" ) add_subdirectory( game-music-emu ) set( GME_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/game-music-emu" ) set( GME_LIBRARIES gme ) @@ -347,8 +350,8 @@ if( WIN32 ) else() set( INSTALL_DOCS_PATH share/doc/${ZDOOM_EXE_NAME} CACHE STRING "Directory where the zdoom documentation will be placed during install." ) endif() -install(DIRECTORY docs/ - DESTINATION ${INSTALL_DOCS_PATH} +install(DIRECTORY docs/ + DESTINATION ${INSTALL_DOCS_PATH} COMPONENT "Documentation") add_subdirectory( lzma ) diff --git a/game-music-emu/CMakeLists.txt b/game-music-emu/CMakeLists.txt index 0af4f24d8..8569b1b7a 100644 --- a/game-music-emu/CMakeLists.txt +++ b/game-music-emu/CMakeLists.txt @@ -4,29 +4,32 @@ project(libgme) include (CheckCXXCompilerFlag) # When version is changed, also change the one in gme/gme.h to match -set(GME_VERSION 0.6.1 CACHE INTERNAL "libgme Version") +set(GME_VERSION 0.6.2 CACHE INTERNAL "libgme Version") # 2.6+ always assumes FATAL_ERROR, but 2.4 and below don't. # Of course, 2.4 might work, in which case you're welcome to drop # down the requirement, but I can't test that. -cmake_minimum_required(VERSION 2.8.7 FATAL_ERROR) - -make_release_only() +cmake_minimum_required(VERSION 2.6 FATAL_ERROR) # I don't plan on debugging this, so make it a release build. if( NOT CMAKE_BUILD_TYPE MATCHES "Release" ) - set( CMAKE_BUILD_TYPE "RelWithDebInfo" ) + set( CMAKE_BUILD_TYPE "RelWithDebInfo" ) endif() if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra" ) - if( NOT PROFILE ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer" ) - endif() - check_cxx_compiler_flag( -Wno-array-bounds HAVE_NO_ARRAY_BOUNDS ) - if( HAVE_NO_ARRAY_BOUNDS ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds" ) - endif() + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra" ) + if( NOT PROFILE ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer" ) + endif() + check_cxx_compiler_flag( -Wno-array-bounds HAVE_NO_ARRAY_BOUNDS ) + if( HAVE_NO_ARRAY_BOUNDS ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds" ) + endif() +endif() + +#[ZDoom] Disable most of bogus and annoying MSVC warnings +if( MSVC ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4101 /wd4800 /wd4702 /wd4706 /wd4805 /wd4310 /wd4244 /wd4456 /wd4459 /wd4146 /wd4127 /wd4458 /wd4267 /wd4804") endif() # Enable fast flag for GME @@ -34,79 +37,102 @@ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ZD_FASTMATH_FLAG}" ) # Default emulators to build (all of them! ;) # [ZDoom] No options, enable all of them by default. + #if (NOT DEFINED USE_GME_AY) - SET(USE_GME_AY 1 BOOL "Enable support for Spectrum ZX music emulation") + SET(USE_GME_AY 1 CACHE BOOL "Enable support for Spectrum ZX music emulation") #endif() #if (NOT DEFINED USE_GME_GBS) - SET(USE_GME_GBS 1 BOOL "Enable support for Game Boy music emulation") + SET(USE_GME_GBS 1 CACHE BOOL "Enable support for Game Boy music emulation") #endif() #if (NOT DEFINED USE_GME_GYM) - SET(USE_GME_GYM 1 BOOL "Enable Sega MegaDrive/Genesis music emulation") + SET(USE_GME_GYM 1 CACHE BOOL "Enable Sega MegaDrive/Genesis music emulation") #endif() #if (NOT DEFINED USE_GME_HES) - SET(USE_GME_HES 1 BOOL "Enable PC Engine/TurboGrafx-16 music emulation") + SET(USE_GME_HES 1 CACHE BOOL "Enable PC Engine/TurboGrafx-16 music emulation") #endif() #if (NOT DEFINED USE_GME_KSS) - SET(USE_GME_KSS 1 BOOL "Enable MSX or other Z80 systems music emulation") + SET(USE_GME_KSS 1 CACHE BOOL "Enable MSX or other Z80 systems music emulation") #endif() #if (NOT DEFINED USE_GME_NSF) - SET(USE_GME_NSF 1 BOOL "Enable NES NSF music emulation") + SET(USE_GME_NSF 1 CACHE BOOL "Enable NES NSF music emulation") #endif() #if (NOT DEFINED USE_GME_NSFE) - SET(USE_GME_NSFE 1 BOOL "Enable NES NSFE and NSF music emulation") + SET(USE_GME_NSFE 1 CACHE BOOL "Enable NES NSFE and NSF music emulation") #endif() #if (NOT DEFINED USE_GME_SAP) - SET(USE_GME_SAP 1 BOOL "Enable Atari SAP music emulation") + SET(USE_GME_SAP 1 CACHE BOOL "Enable Atari SAP music emulation") #endif() #if (NOT DEFINED USE_GME_SPC) - SET(USE_GME_SPC 1 BOOL "Enable SNES SPC music emulation") + SET(USE_GME_SPC 1 CACHE BOOL "Enable SNES SPC music emulation") #endif() #if (NOT DEFINED USE_GME_VGM) - SET(USE_GME_VGM 1 BOOL "Enable Sega VGM/VGZ music emulation") + SET(USE_GME_VGM 1 CACHE BOOL "Enable Sega VGM/VGZ music emulation") +#endif() + +#if (NOT DEFINED GME_YM2612_EMU) + SET(GME_YM2612_EMU "Nuked" CACHE STRING "Which YM2612 emulator to use: \"Nuked\" (LGPLv2.1+), \"MAME\" (GPLv2+), or \"GENS\" (LGPLv2.1+)") #endif() #if (USE_GME_NSFE AND NOT USE_GME_NSF) - # MESSAGE(" -- NSFE support requires NSF, enabling NSF support. --") - SET(USE_GME_NSF 1 BOOL "Enable NES NSF music emulation") + #MESSAGE(" -- NSFE support requires NSF, enabling NSF support. --") + SET(USE_GME_NSF 1 CACHE BOOL "Enable NES NSF music emulation" FORCE) #endif() # [ZDoom] Set always to OFF. set(BUILD_SHARED_LIBS OFF) +set(ENABLE_UBSAN OFF) -# Check for GCC "visibility" support. -if (CMAKE_COMPILER_IS_GNUCXX) - check_cxx_compiler_flag (-fvisibility=hidden __LIBGME_TEST_VISIBILITY) - set (ENABLE_VISIBILITY OFF) - if (__LIBGME_TEST_VISIBILITY) - # get the gcc version - exec_program(${CMAKE_CXX_COMPILER} ARGS --version OUTPUT_VARIABLE _gcc_version_info) - string (REGEX MATCH "[3-9]\\.[0-9]\\.[0-9]" _gcc_version "${_gcc_version_info}") +# Check for GCC/Clang "visibility" support. +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" + OR + CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # gcc <4.1 had poor support for symbol visibility - if ((${_gcc_version} VERSION_GREATER "4.1") OR (${_gcc_version} VERSION_EQUAL "4.1")) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") - set (ENABLE_VISIBILITY ON) - add_definitions (-DLIBGME_VISIBILITY) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -W -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - # GCC >= 4.2 also correctly supports making inline members have hidden - # visibility by default. - if ((${_gcc_version} VERSION_GREATER "4.2") OR (${_gcc_version} VERSION_EQUAL "4.2")) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") - endif() - endif() - endif() # test visibility + # Assume we have visibility support on any compiler that supports C++11 + add_definitions (-DLIBGME_VISIBILITY) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden") - # Cache this result - set( LIBGME_HAVE_GCC_VISIBILITY ${ENABLE_VISIBILITY} CACHE BOOL "GCC support for hidden visibility") + # Try to protect against undefined behavior from signed integer overflow + # This has caused miscompilation of code already and there are other + # potential uses; see https://bitbucket.org/mpyne/game-music-emu/issues/18/ + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fwrapv") + + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if (NOT DEFINED LIBGME_SWITCH_FALLTHROUGH) + check_cxx_compiler_flag (-Wimplicit-fallthrough __LIBGME_SWITCH_FALLTHROUGH_WARNINGS) + set (LIBGME_SWITCH_FALLTHROUGH ${__LIBGME_SWITCH_FALLTHROUGH_WARNINGS} + CACHE BOOL "Set if the compiler will complain about implicit switch fallthrough" + ) + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-inconsistent-missing-override -Wno-unused-const-variable") + endif() + + if (ENABLE_UBSAN) + # GCC needs -static-libubsan + if (NOT BUILD_SHARED_LIBS AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -static-libubsan") + else() + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") + endif() + endif() +endif () + +if(LIBGME_SWITCH_FALLTHROUGH) + # Avoid warning spam about switch fallthroughs, which are numerous in + # the codebase. + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wimplicit-fallthrough=0") endif() # Shared library defined here diff --git a/game-music-emu/changes.txt b/game-music-emu/changes.txt index 48986756a..034ba4821 100644 --- a/game-music-emu/changes.txt +++ b/game-music-emu/changes.txt @@ -1,274 +1,5 @@ Game_Music_Emu Change Log ------------------------- -Game_Music_Emu 0.6.1 --------------------- - -- Moved repository to Bitbucket since Google Code announced they would - shutdown this year. - -- Packaging improvements: - - Honor $LIB_SUFFIX for installed pkg-config metadata. - - Support setting BUILD_SHARED_LIBS to OFF to build libgme as a static - library. (Pass -DBUILD_SHARED_LIBS=OFF when running cmake). - Thanks to lachs0r. - -Game_Music_Emu 0.6.0 --------------------- - -- Note: A 0.5.6 release was referenced but never tagged or packaged. - -- SPC improvements: - - Switched to newer snes_spc 0.9.0 for SPC emulation. Uses fast DSP. - - Fixed Spc_Emu::gain(). - - Fixed support for files <0x10200 bytes. - -- Other bugfixes: - - Fixed a couple of GBS bugs, one involving access of memory after - realloc. - - Blip_Buffer works on systems where 'double' is a single-precision - floating-point type. - - Fix uninitialized buffer size in dual_resampler. - - Compilation warnings squashed out as of clang 3.3-pre and gcc 4.7.2. - -- API changes/additions: - - Removed documentation of C++ interface, as the C interface in gme.h is - the only supported one. - - Added gme_enable_accuracy() for enabling more accurate sound emulation - options (currently affects SPC only). - -- Build system improvements: - - Add pkg_config support. - - Fix build on case-insensitive systems. - - Allow for install on Cygwin. - - Fix install on multilib systems, such as many 64-bit distros (CMake must - be able to figure out your system's libsuffix, if any). - - C++ implementation symbols are not leaked into the resultant library - file (requires symbol visibility support). - -- Sample player improvements: - - Can toggle fast/accurate emulation (with the 'A' key). - -Game_Music_Emu 0.5.5 --------------------- -- CMake build support has been added. You can build Game_Music_Emu as -a shared library and install it so that you do not have to include your -own copy if you know libgme will be present on your target system. -Requires CMake 2.6 or higher. - - -Game_Music_Emu 0.5.2 --------------------- -- *TONS* of changes and improvements. You should re-read the new header -files and documentation as the changes will allow you to simplify your -code a lot (it might even be simpler to just rewrite it). Existing code -should continue to work without changes in most cases (see Deprecated -features in gme.txt). - -- New file formats: AY, HES, KSS, SAP, NSFE - -- All-new comprehensive C interface (also usable from C++). Simplifies -many things, especially file loading, and brings everything together in -one header file (gme.h). - -- Information tags and track names and times can be accessed for all -game music formats - -- New features supported by all emulators: end of track fading, -automatic silence detection, adjustable song tempo, seek to new time in -track - -- Updated mini player example to support track names and times, echo, -tempo, and channel muting, and added visual waveform display - -- Improved configuration to use blargg_config.h, which you can modify -and keep when you update to a newer libary version. Includes flag for -library to automatically handle gzipped files using zlib (so you don't -need to use Gzip_File_Reader anymore). - -- GBS: Fixed wave channel to not reset waveform when APU is powered off -(affected Garfield). Also improved invalid bank selection (affected Game -& Watch and others). - -- VGM: Added support for alternate noise shifter register -configurations, used by other systems like the BBC Micro. - -- SPC: Removed IPL ROM dump from emulator, as none of the SPC files I -scanned needed it, and an SPC file can include a copy if necessary. Also -re-enabled supposed clamping in gaussian interpolation between the third -and fourth lookups, though I don't know whether it matters - -- Added Music_Emu::load_mem() to use music data already in memory -(without copying it) - -- Added Music_Emu::warning(), which reports minor problems when loading -and playing a music file - -- Added Music_Emu::set_gain() for uniform adjustment of gain. Can only -be set during initialization, so not useful as a general volume control. - -- Added custom operator new to ensure that no exceptions are thrown in -the library (I'd use std::nothrow if it were part of pre-ISO (ARM) C++) - -- Added BLIP_BUFFER_FAST flag to blargg_config.h to use a lower quality -bandlimited synthesis in "classic" emulators, which might help -performance on ancient processors (measure first!). Don't use this -unless absolutely necessary, as quality suffers. - -- Improved performance a bit for x86 platforms - -- Text files now in DOS newline format so they will open in Notepad -properly - -- Removed requirement that file header structures not have any padding -added to the end - -- Fixed common bug in all CPU emulators where negative program counter -could crash emulator (occurred during a negative branch from the -beginning of memory). Also fixed related bug in Z80 emulator for -IX/IY+displacement mode. - -- Eliminated all warnings when compiling on gcc 4.0. The following -generates no diagnostics: - - gcc -S gme/*.cpp -o /dev/null -ansi -fno-gnu-keywords - -fno-nonansi-builtins -pedantic -W -Wabi -Wall -Wcast-align - -Wcast-qual -Wchar-subscripts -Wdisabled-optimization -Werror - -Winline -Wlong-long -Wmultichar -Winvalid-offsetof - -Wnon-virtual-dtor -Woverloaded-virtual -Wparentheses - -Wpointer-arith -Wredundant-decls -Wreorder -Wsign-compare - -Wsign-promo -Wunknown-pragmas -Wwrite-strings - - -Game_Music_Emu 0.3.0 --------------------- -- Added more demos, including music player using the SDL multimedia -library for sound, and improved documentation - -- All: Improved interface to emulators to allow simpler setup and -loading. Instead of various init() functions, all now support -set_sample_rate( long rate ) and load( const char* file_path ). - -- All: Removed error return from start_track() and play(), and added -error_count() to get the total number of emulation errors since the -track was last started. See demos for examples of new usage. - -- All: Fixed mute_voices() muting to be preserved after loading files -and starting tracks, instead of being cleared as it was whenever a track -was started - -- VGM: Rewrote Vgm_Emu to support Sega Genesis/Mega Drive FM sound at -any sample rate with optional FM oversampling, support for alternate -YM2612 sound cores, and support for optional YM2413 - -- VGM: Added tempo control, useful for slowing 60Hz NTSC Sega Genesis -music to 50Hz PAL - -- VGM: Removed Vgm_Emu::track_data(), since I realized that this -information is already present in the VGM header (oops!) - -- GYM: Changed Gym_Emu::track_length() operation (see Gym_Emu.h) - -- NSF: Added support for Sunsoft FME-7 sound chip used by Gimmick -soundtrack - -- NSF: Fixed Namco 106 problems with Final Lap and others - -- Moved library sources to gme/ directory to reduce clutter, and merged -boost/ functionality into blargg_common.h - -- Added Gzip_File_Reader for transparently using gzipped files - - -Game_Music_Emu 0.2.4 --------------------- -- Created a discussion forum for problems and feedback: -http://groups-beta.google.com/group/blargg-sound-libs - -- Changed error return value of Blip_Buffer::sample_rate() (also for -Stereo_Buffer, Effects_Buffer, etc.) to blargg_err_t (defined in -blargg_common.h), to make error reporting consistent with other -functions. This means the "no error" return value is the opposite of -what it was before, which will break current code which checks the error -return value: - - // current code (broken) - if ( !buf.sample_rate( samples_per_sec ) ) - out_of_memory(); - - // quick-and-dirty fix (just remove the ! operation) - if ( buf.sample_rate( samples_per_sec ) ) - out_of_memory(); - - // proper fix - blargg_err_t error = buf.sample_rate( samples_per_sec ); - if ( error ) - report_error( error ); - -- Implemented workaround for MSVC++ 6 compiler limitations, allowing it -to work on that compiler again - -- Added sample clamping to avoid wrap-around at high volumes, allowing -higher volume with little distortion - -- Added to-do list and design notes - -- Added Music_Emu::skip( long sample_count ) to skip ahead in current -track - -- Added Gym_Emu::track_length() and Vgm_Emu::track_length() for -determining the length of non-looped GYM and VGM files - -- Partially implemented DMC non-linearity when its value is directly set -using $4011, which reduces previously over-emphasized "popping" of -percussion on some games (TMNT II in particular) - -- Fixed Fir_Resampler, used for SPC and GYM playback (was incorrectly -using abs() instead of fabs()...argh) - -- Fixed SPC emulation bugs: eliminated clicks in Plok! soundtrack and -now stops sample slightly earlier than the end, as the SNES does. Fixed -a totally broken CPU addressing mode. - -- Fixed Konami VRC6 saw wave (was very broken before). Now VRC6 music -sounds decent - -- Fixed a minor GBS emulation bug - -- Fixed GYM loop point bug when track was restarted before loop point -had been reached - -- Made default GBS frequency equalization less muffled - -- Added pseudo-surround effect removal for SPC files - -- Added Music_Emu::voice_names() which returns names for each voice. - -- Added BLARGG_SOURCE_BEGIN which allows custom compiler options to be -easily set for library sources - -- Changed assignment of expansion sound chips in Nsf_Emu to be spread -more evenly when using Effects_Buffer - -- Changed 'size_t' values in Blip_Buffer interface to 'long' - -- Changed demo to generate a WAVE sound file rather than an AIFF file - - -Game_Music_Emu 0.2.0 --------------------- -- Redid framework and rewrote/cleaned up emulators - -- Changed licensing to GNU Lesser General Public License (LGPL) - -- Added Sega Genesis GYM and Super Nintendo SPC emulators - -- Added Namco-106 and Konami VRC6 sound chip support to NSF emulator - -- Eliminated use of static mutable data in emulators, allowing -multi-instance safety - - -Game_Music_Emu 0.1.0 --------------------- -- First release +Please see the git version history (e.g. git shortlog tags/0.6.0..tags/0.6.1) +for the accurate change log. diff --git a/game-music-emu/gme.txt b/game-music-emu/gme.txt index baec6f4dc..5a7d2f560 100644 --- a/game-music-emu/gme.txt +++ b/game-music-emu/gme.txt @@ -1,9 +1,8 @@ -Game_Music_Emu 0.6.1 +Game_Music_Emu 0.6.2 -------------------- Author : Shay Green Maintainer : Michael Pyne -Website : http://www.slack.net/~ant/libs/ -Forum : http://groups.google.com/group/blargg-sound-libs +Website : https://bitbucket.org/mpyne/game-music-emu/ Source : https://bitbucket.org/mpyne/game-music-emu/ License : GNU Lesser General Public License (LGPL), see LICENSE.txt diff --git a/game-music-emu/gme/Ay_Apu.cpp b/game-music-emu/gme/Ay_Apu.cpp index d58d839fe..d132c42f9 100644 --- a/game-music-emu/gme/Ay_Apu.cpp +++ b/game-music-emu/gme/Ay_Apu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Ay_Apu.h" @@ -299,7 +299,7 @@ void Ay_Apu::run_until( blip_time_t final_end_time ) while ( ntime <= end ) // must advance *past* time to avoid hang { int changed = noise_lfsr + 1; - noise_lfsr = ((blargg_ulong)-(blargg_long)(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); + noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); if ( changed & 2 ) { delta = -delta; diff --git a/game-music-emu/gme/Ay_Apu.h b/game-music-emu/gme/Ay_Apu.h index b031f0473..ad2d83692 100644 --- a/game-music-emu/gme/Ay_Apu.h +++ b/game-music-emu/gme/Ay_Apu.h @@ -1,6 +1,6 @@ // AY-3-8910 sound chip emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef AY_APU_H #define AY_APU_H diff --git a/game-music-emu/gme/Ay_Cpu.cpp b/game-music-emu/gme/Ay_Cpu.cpp index cdc3947f6..31c912568 100644 --- a/game-music-emu/gme/Ay_Cpu.cpp +++ b/game-music-emu/gme/Ay_Cpu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ /* Last validated with zexall 2006.11.21 5:26 PM @@ -136,11 +136,6 @@ static byte const ed_dd_timing [0x100] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, }; -// even on x86, using short and unsigned char was slower -typedef int fint16; -typedef unsigned fuint16; -typedef unsigned fuint8; - bool Ay_Cpu::run( cpu_time_t end_time ) { set_end_time( end_time ); @@ -148,8 +143,6 @@ bool Ay_Cpu::run( cpu_time_t end_time ) this->state = &s; bool warning = false; - typedef BOOST::int8_t int8_t; - union { regs_t rg; pairs_t rp; @@ -160,10 +153,10 @@ bool Ay_Cpu::run( cpu_time_t end_time ) cpu_time_t s_time = s.time; uint8_t* const mem = this->mem; // cache - fuint16 pc = r.pc; - fuint16 sp = r.sp; - fuint16 ix = r.ix; // TODO: keep in memory for direct access? - fuint16 iy = r.iy; + uint16_t pc = r.pc; + uint16_t sp = r.sp; + uint16_t ix = r.ix; // TODO: keep in memory for direct access? + uint16_t iy = r.iy; int flags = r.b.flags; goto loop; @@ -182,7 +175,7 @@ loop: check( (unsigned) ix < 0x10000 ); check( (unsigned) iy < 0x10000 ); - fuint8 opcode; + uint8_t opcode; opcode = READ_PROG( pc ); pc++; @@ -206,7 +199,7 @@ loop: 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F }; - fuint16 data; + uint16_t data; data = base_timing [opcode]; if ( (s_time += data) >= 0 ) goto possibly_out_of_time; @@ -262,7 +255,7 @@ possibly_out_of_time: goto loop; case 0x3A:{// LD A,(addr) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; rg.a = READ( addr ); goto loop; @@ -277,7 +270,7 @@ possibly_out_of_time: // JR #define JR( cond ) {\ - int disp = (BOOST::int8_t) data;\ + int disp = (int8_t) data;\ pc++;\ if ( !(cond) )\ goto jr_not_taken;\ @@ -349,7 +342,7 @@ possibly_out_of_time: case 0xCD:{// CALL addr call_taken: - fuint16 addr = pc + 2; + uint16_t addr = pc + 2; pc = GET_ADDR(); sp = uint16_t (sp - 2); WRITE_WORD( sp, addr ); @@ -469,7 +462,7 @@ possibly_out_of_time: add_hl_data: { blargg_ulong sum = rp.hl + data; data ^= rp.hl; - rp.hl = (uint16_t)sum; + rp.hl = sum; flags = (flags & (S80 | Z40 | V04)) | (sum >> 16) | (sum >> 8 & (F20 | F08)) | @@ -659,21 +652,21 @@ possibly_out_of_time: goto loop; case 0x2A:{// LD HL,(addr) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; rp.hl = READ_WORD( addr ); goto loop; } case 0x32:{// LD (addr),A - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE( addr, rg.a ); goto loop; } case 0x22:{// LD (addr),HL - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE_WORD( addr, rp.hl ); goto loop; @@ -696,7 +689,7 @@ possibly_out_of_time: // Rotate case 0x07:{// RLCA - fuint16 temp = rg.a; + uint16_t temp = rg.a; temp = (temp << 1) | (temp >> 7); flags = (flags & (S80 | Z40 | P04)) | (temp & (F20 | F08 | C01)); @@ -705,7 +698,7 @@ possibly_out_of_time: } case 0x0F:{// RRCA - fuint16 temp = rg.a; + uint16_t temp = rg.a; flags = (flags & (S80 | Z40 | P04)) | (temp & C01); temp = (temp << 7) | (temp >> 1); @@ -719,12 +712,12 @@ possibly_out_of_time: flags = (flags & (S80 | Z40 | P04)) | (temp & (F20 | F08)) | (temp >> 8); - rg.a = (uint8_t)temp; + rg.a = temp; goto loop; } case 0x1F:{// RRA - fuint16 temp = (flags << 7) | (rg.a >> 1); + uint16_t temp = (flags << 7) | (rg.a >> 1); flags = (flags & (S80 | Z40 | P04)) | (temp & (F20 | F08)) | (rg.a & C01); @@ -734,7 +727,7 @@ possibly_out_of_time: // Misc case 0x2F:{// CPL - fuint16 temp = ~rg.a; + uint16_t temp = ~rg.a; flags = (flags & (S80 | Z40 | P04 | C01)) | (temp & (F20 | F08)) | (H10 | N02); @@ -760,21 +753,21 @@ possibly_out_of_time: goto loop; case 0xE3:{// EX (SP),HL - fuint16 temp = READ_WORD( sp ); + uint16_t temp = READ_WORD( sp ); WRITE_WORD( sp, rp.hl ); rp.hl = temp; goto loop; } case 0xEB:{// EX DE,HL - fuint16 temp = rp.hl; + uint16_t temp = rp.hl; rp.hl = rp.de; rp.de = temp; goto loop; } case 0xD9:{// EXX DE,HL - fuint16 temp = r.alt.w.bc; + uint16_t temp = r.alt.w.bc; r.alt.w.bc = rp.bc; rp.bc = temp; @@ -815,7 +808,7 @@ possibly_out_of_time: // Rotate left #define RLC( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ result = uint8_t (result << 1) | (result >> 7);\ flags = SZ28P( result ) | (result & C01);\ write;\ @@ -834,7 +827,7 @@ possibly_out_of_time: } #define RL( read, write ) {\ - fuint16 result = (read << 1) | (flags & C01);\ + uint16_t result = (read << 1) | (flags & C01);\ flags = SZ28PC( result );\ write;\ goto loop;\ @@ -852,7 +845,7 @@ possibly_out_of_time: } #define SLA( read, add, write ) {\ - fuint16 result = (read << 1) | add;\ + uint16_t result = (read << 1) | add;\ flags = SZ28PC( result );\ write;\ goto loop;\ @@ -883,7 +876,7 @@ possibly_out_of_time: // Rotate right #define RRC( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ flags = result & C01;\ result = uint8_t (result << 7) | (result >> 1);\ flags |= SZ28P( result );\ @@ -903,8 +896,8 @@ possibly_out_of_time: } #define RR( read, write ) {\ - fuint8 result = read;\ - fuint8 temp = result & C01;\ + uint8_t result = read;\ + uint8_t temp = result & C01;\ result = uint8_t (flags << 7) | (result >> 1);\ flags = SZ28P( result ) | temp;\ write;\ @@ -923,7 +916,7 @@ possibly_out_of_time: } #define SRA( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ flags = result & C01;\ result = (result & 0x80) | (result >> 1);\ flags |= SZ28P( result );\ @@ -943,7 +936,7 @@ possibly_out_of_time: } #define SRL( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ flags = result & C01;\ result >>= 1;\ flags |= SZ28P( result );\ @@ -1048,7 +1041,7 @@ possibly_out_of_time: blargg_ulong sum = temp + (flags & C01); flags = ~data >> 2 & N02; if ( flags ) - sum = (blargg_ulong)-(blargg_long)sum; + sum = -sum; sum += rp.hl; temp ^= rp.hl; temp ^= sum; @@ -1056,7 +1049,7 @@ possibly_out_of_time: (temp >> 8 & H10) | (sum >> 8 & (S80 | F20 | F08)) | ((temp - -0x8000) >> 14 & V04); - rp.hl = (uint16_t)sum; + rp.hl = sum; if ( (uint16_t) sum ) goto loop; flags |= Z40; @@ -1084,7 +1077,7 @@ possibly_out_of_time: case 0x43: // LD (ADDR),BC case 0x53: // LD (ADDR),DE temp = R16( data, 4, 0x43 ); - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE_WORD( addr, temp ); goto loop; @@ -1092,21 +1085,21 @@ possibly_out_of_time: case 0x4B: // LD BC,(ADDR) case 0x5B:{// LD DE,(ADDR) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; R16( data, 4, 0x4B ) = READ_WORD( addr ); goto loop; } case 0x7B:{// LD SP,(ADDR) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; sp = READ_WORD( addr ); goto loop; } case 0x67:{// RRD - fuint8 temp = READ( rp.hl ); + uint8_t temp = READ( rp.hl ); WRITE( rp.hl, (rg.a << 4) | (temp >> 4) ); temp = (rg.a & 0xF0) | (temp & 0x0F); flags = (flags & C01) | SZ28P( temp ); @@ -1115,7 +1108,7 @@ possibly_out_of_time: } case 0x6F:{// RLD - fuint8 temp = READ( rp.hl ); + uint8_t temp = READ( rp.hl ); WRITE( rp.hl, (temp << 4) | (rg.a & 0x0F) ); temp = (rg.a & 0xF0) | (temp >> 4); flags = (flags & C01) | SZ28P( temp ); @@ -1139,7 +1132,7 @@ possibly_out_of_time: case 0xA1: // CPI case 0xB1: // CPIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = READ( addr ); @@ -1172,7 +1165,7 @@ possibly_out_of_time: case 0xA0: // LDI case 0xB0: // LDIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = READ( addr ); @@ -1204,7 +1197,7 @@ possibly_out_of_time: case 0xA3: // OUTI case 0xB3: // OTIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = READ( addr ); @@ -1230,7 +1223,7 @@ possibly_out_of_time: case 0xB2: // INIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = IN( rp.bc ); @@ -1295,7 +1288,7 @@ possibly_out_of_time: //////////////////////////////////////// DD/FD prefix { - fuint16 ixy; + uint16_t ixy; case 0xDD: ixy = ix; goto ix_prefix; @@ -1490,7 +1483,7 @@ possibly_out_of_time: goto loop; case 0x22:{// LD (ADDR),IXY - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE_WORD( addr, ixy ); goto loop; @@ -1502,7 +1495,7 @@ possibly_out_of_time: goto set_ixy; case 0x2A:{// LD IXY,(addr) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); ixy = READ_WORD( addr ); pc += 2; goto set_ixy; @@ -1526,7 +1519,7 @@ possibly_out_of_time: case 0x3E: goto srl_data_addr; // SRL (IXY) CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) - fuint8 temp = READ( data ); + uint8_t temp = READ( data ); int masked = temp & 1 << (data2 >> 3 & 7); flags = (flags & C01) | H10 | (masked & S80) | @@ -1628,7 +1621,7 @@ possibly_out_of_time: goto loop; case 0xE3:{// EX (SP),IXY - fuint16 temp = READ_WORD( sp ); + uint16_t temp = READ_WORD( sp ); WRITE_WORD( sp, ixy ); ixy = temp; goto set_ixy; diff --git a/game-music-emu/gme/Ay_Cpu.h b/game-music-emu/gme/Ay_Cpu.h index cd3d66747..6984b42dc 100644 --- a/game-music-emu/gme/Ay_Cpu.h +++ b/game-music-emu/gme/Ay_Cpu.h @@ -1,6 +1,6 @@ // Z80 CPU emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef AY_CPU_H #define AY_CPU_H @@ -28,9 +28,6 @@ public: void set_time( cpu_time_t t ) { state->time = t - state->base; } void adjust_time( int delta ) { state->time += delta; } - typedef BOOST::uint8_t uint8_t; - typedef BOOST::uint16_t uint16_t; - #if BLARGG_BIG_ENDIAN struct regs_t { uint8_t b, c, d, e, h, l, flags, a; }; #else diff --git a/game-music-emu/gme/Ay_Emu.cpp b/game-music-emu/gme/Ay_Emu.cpp index 565559441..a973ba0f1 100644 --- a/game-music-emu/gme/Ay_Emu.cpp +++ b/game-music-emu/gme/Ay_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Ay_Emu.h" @@ -47,10 +47,10 @@ Ay_Emu::~Ay_Emu() { } static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size ) { - long pos = long(ptr - (byte const*) file.header); - long file_size = long(file.end - (byte const*) file.header); + long pos = ptr - (byte const*) file.header; + long file_size = file.end - (byte const*) file.header; assert( (unsigned long) pos <= (unsigned long) file_size - 2 ); - int offset = (BOOST::int16_t) get_be16( ptr ); + int offset = (int16_t) get_be16( ptr ); if ( !offset || blargg_ulong (pos + offset) > blargg_ulong (file_size - min_size) ) return 0; return ptr + offset; @@ -117,7 +117,7 @@ static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; } static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; } static gme_type_t_ const gme_ay_type_ = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 }; -gme_type_t const gme_ay_type = &gme_ay_type_; +BLARGG_EXPORT extern gme_type_t const gme_ay_type = &gme_ay_type_; // Setup @@ -207,7 +207,7 @@ blargg_err_t Ay_Emu::start_track_( int track ) if ( len > blargg_ulong (file.end - in) ) { set_warning( "Missing file data" ); - len = unsigned(file.end - in); + len = file.end - in; } //debug_printf( "addr: $%04X, len: $%04X\n", addr, len ); if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data diff --git a/game-music-emu/gme/Ay_Emu.h b/game-music-emu/gme/Ay_Emu.h index 86b020487..6726f0157 100644 --- a/game-music-emu/gme/Ay_Emu.h +++ b/game-music-emu/gme/Ay_Emu.h @@ -1,6 +1,6 @@ // Sinclair Spectrum AY music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef AY_EMU_H #define AY_EMU_H diff --git a/game-music-emu/gme/Blip_Buffer.h b/game-music-emu/gme/Blip_Buffer.h index 4cc526d2f..e6facc820 100644 --- a/game-music-emu/gme/Blip_Buffer.h +++ b/game-music-emu/gme/Blip_Buffer.h @@ -95,6 +95,8 @@ public: Blip_Buffer(); ~Blip_Buffer(); + Blip_Buffer(Blip_Buffer &&) = default; + // Deprecated typedef blip_resampled_time_t resampled_time_t; blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); } diff --git a/game-music-emu/gme/CMakeLists.txt b/game-music-emu/gme/CMakeLists.txt index 8d710709f..a5e06bc8a 100644 --- a/game-music-emu/gme/CMakeLists.txt +++ b/game-music-emu/gme/CMakeLists.txt @@ -14,6 +14,15 @@ set(libgme_SRCS Blip_Buffer.cpp Music_Emu.cpp ) +# static builds need to find static zlib (and static forms of other needed +# libraries. Ensure CMake looks only for static libs if we're doing a static +# build. See https://stackoverflow.com/a/44738756 +if(NOT BUILD_SHARED_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +endif() + +find_package(ZLIB QUIET) + # Ay_Apu is very popular around here if (USE_GME_AY OR USE_GME_KSS) set(libgme_SRCS ${libgme_SRCS} @@ -23,9 +32,25 @@ endif() # so is Ym2612_Emu if (USE_GME_VGM OR USE_GME_GYM) - set(libgme_SRCS ${libgme_SRCS} - Ym2612_Emu.cpp - ) + if(GME_YM2612_EMU STREQUAL "Nuked") + add_definitions(-DVGM_YM2612_NUKED) + set(libgme_SRCS ${libgme_SRCS} + Ym2612_Nuked.cpp + ) + message("VGM/GYM: Nuked OPN2 emulator will be used") + elseif(GME_YM2612_EMU STREQUAL "MAME") + add_definitions(-DVGM_YM2612_MAME) + set(libgme_SRCS ${libgme_SRCS} + Ym2612_MAME.cpp + ) + message("VGM/GYM: MAME YM2612 emulator will be used") + else() + add_definitions(-DVGM_YM2612_GENS) + set(libgme_SRCS ${libgme_SRCS} + Ym2612_GENS.cpp + ) + message("VGM/GYM: GENS 2.10 emulator will be used") + endif() endif() # But none are as popular as Sms_Apu @@ -127,19 +152,11 @@ endif() # These headers are part of the generic gme interface. set (EXPORTED_HEADERS gme.h) -# Run during cmake phase, so this is available during make -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gme_types.h.in - ${CMAKE_CURRENT_BINARY_DIR}/gme_types.h) - -# [ZDoom] Not needed. -if( FALSE ) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libgme.pc.in - ${CMAKE_CURRENT_BINARY_DIR}/libgme.pc @ONLY) -endif() - # On some platforms we may need to change headers or whatnot based on whether # we're building the library or merely using the library. The following is # only defined when building the library to allow us to tell which is which. + +#[ZDoom] Not needed #add_definitions(-DBLARGG_BUILD_DLL) # For the gme_types.h @@ -148,13 +165,25 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Add library to be compiled. add_library(gme ${libgme_SRCS}) +if(ZLIB_FOUND) + message(" ** ZLib library located, compressed file formats will be supported") + target_compile_definitions(gme PRIVATE -DHAVE_ZLIB_H) + target_include_directories(gme PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(gme ${ZLIB_LIBRARIES}) + # Is not to be installed though + + set(PKG_CONFIG_ZLIB -lz) # evaluated in libgme.pc.in +else() + message("ZLib library not found, disabling support for compressed formats such as VGZ") +endif() + +# [ZDoom] Not needed. +if( FALSE ) # The version is the release. The "soversion" is the API version. As long # as only build fixes are performed (i.e. no backwards-incompatible changes # to the API), the SOVERSION should be the same even when bumping up VERSION. # The way gme.h is designed, SOVERSION should very rarely be bumped, if ever. # Hopefully the API can stay compatible with old versions. -# [ZDoom] Not needed. -if( FALSE ) set_target_properties(gme PROPERTIES VERSION ${GME_VERSION} SOVERSION 0) @@ -163,8 +192,13 @@ install(TARGETS gme LIBRARY DESTINATION lib${LIB_SUFFIX} RUNTIME DESTINATION bin # DLL platforms ARCHIVE DESTINATION lib) # DLL platforms +# Run during cmake phase, so this is available during make +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gme_types.h.in + ${CMAKE_CURRENT_BINARY_DIR}/gme_types.h) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libgme.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/libgme.pc @ONLY) + install(FILES ${EXPORTED_HEADERS} DESTINATION include/gme) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgme.pc DESTINATION lib${LIB_SUFFIX}/pkgconfig) endif() - -target_link_libraries(gme) diff --git a/game-music-emu/gme/Classic_Emu.cpp b/game-music-emu/gme/Classic_Emu.cpp index 42bb2fbe2..c572d9b5c 100644 --- a/game-music-emu/gme/Classic_Emu.cpp +++ b/game-music-emu/gme/Classic_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Classic_Emu.h" @@ -54,6 +54,12 @@ blargg_err_t Classic_Emu::set_sample_rate_( long rate ) return buf->set_sample_rate( rate, 1000 / 20 ); } +blargg_err_t Classic_Emu::set_multi_channel ( bool is_enabled ) +{ + RETURN_ERR( Music_Emu::set_multi_channel_( is_enabled ) ); + return 0; +} + void Classic_Emu::mute_voices_( int mask ) { Music_Emu::mute_voices_( mask ); diff --git a/game-music-emu/gme/Classic_Emu.h b/game-music-emu/gme/Classic_Emu.h index 99e99afbe..57cdd5c32 100644 --- a/game-music-emu/gme/Classic_Emu.h +++ b/game-music-emu/gme/Classic_Emu.h @@ -1,6 +1,6 @@ // Common aspects of emulators which use Blip_Buffer for sound output -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef CLASSIC_EMU_H #define CLASSIC_EMU_H @@ -13,6 +13,7 @@ public: Classic_Emu(); ~Classic_Emu(); void set_buffer( Multi_Buffer* ); + blargg_err_t set_multi_channel( bool is_enabled ) override; protected: // Services enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; diff --git a/game-music-emu/gme/Data_Reader.cpp b/game-music-emu/gme/Data_Reader.cpp index 67fc8aefd..1556c329f 100644 --- a/game-music-emu/gme/Data_Reader.cpp +++ b/game-music-emu/gme/Data_Reader.cpp @@ -1,7 +1,5 @@ // File_Extractor 0.4.0. http://www.slack.net/~ant/ -#define _CRT_SECURE_NO_WARNINGS - #include "Data_Reader.h" #include "blargg_endian.h" @@ -22,24 +20,38 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" +#ifdef HAVE_ZLIB_H +#include +#include +#include +static const unsigned char gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ +#endif /* HAVE_ZLIB_H */ + const char Data_Reader::eof_error [] = "Unexpected end of file"; +#define RETURN_VALIDITY_CHECK( cond ) \ + do { if ( unlikely( !(cond) ) ) return "Corrupt file"; } while(0) + blargg_err_t Data_Reader::read( void* p, long s ) { + RETURN_VALIDITY_CHECK( s > 0 ); + long result = read_avail( p, s ); if ( result != s ) { if ( result >= 0 && result < s ) return eof_error; - + return "Read error"; } - + return 0; } blargg_err_t Data_Reader::skip( long count ) { + RETURN_VALIDITY_CHECK( count >= 0 ); + char buf [512]; while ( count ) { @@ -56,7 +68,8 @@ long File_Reader::remain() const { return size() - tell(); } blargg_err_t File_Reader::skip( long n ) { - assert( n >= 0 ); + RETURN_VALIDITY_CHECK( n >= 0 ); + if ( !n ) return 0; return seek( tell() + n ); @@ -69,13 +82,14 @@ Subset_Reader::Subset_Reader( Data_Reader* dr, long size ) in = dr; remain_ = dr->remain(); if ( remain_ > size ) - remain_ = size; + remain_ = max( 0l, size ); } long Subset_Reader::remain() const { return remain_; } long Subset_Reader::read_avail( void* p, long s ) { + s = max( 0l, s ); if ( s > remain_ ) s = remain_; remain_ -= s; @@ -87,30 +101,32 @@ long Subset_Reader::read_avail( void* p, long s ) Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r ) { header = (char const*) h; - header_end = header + size; + header_end = header + max( 0l, size ); in = r; } -long Remaining_Reader::remain() const { return long(header_end - header + in->remain()); } +long Remaining_Reader::remain() const { return header_end - header + in->remain(); } long Remaining_Reader::read_first( void* out, long count ) { - long first = long(header_end - header); + count = max( 0l, count ); + long first = header_end - header; if ( first ) { - if ( first > count ) + if ( first > count || first < 0 ) first = count; void const* old = header; header += first; - memcpy( out, old, first ); + memcpy( out, old, (size_t) first ); } return first; } long Remaining_Reader::read_avail( void* out, long count ) { + count = max( 0l, count ); long first = read_first( out, count ); - long second = count - first; + long second = max( 0l, count - first ); if ( second ) { second = in->read_avail( (char*) out + first, second ); @@ -122,8 +138,9 @@ long Remaining_Reader::read_avail( void* out, long count ) blargg_err_t Remaining_Reader::read( void* out, long count ) { + count = max( 0l, count ); long first = read_first( out, count ); - long second = count - first; + long second = max( 0l, count - first ); if ( !second ) return 0; return in->read( (char*) out + first, second ); @@ -132,41 +149,135 @@ blargg_err_t Remaining_Reader::read( void* out, long count ) // Mem_File_Reader Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : - begin( (const char*) p ), - size_( s ) + m_begin( (const char*) p ), + m_size( max( 0l, s ) ), + m_pos( 0l ) { - pos = 0; +#ifdef HAVE_ZLIB_H + if( !m_begin ) + return; + + if ( gz_decompress() ) + { + debug_printf( "Loaded compressed data\n" ); + m_ownedPtr = true; + } +#endif /* HAVE_ZLIB_H */ } - -long Mem_File_Reader::size() const { return size_; } + +#ifdef HAVE_ZLIB_H +Mem_File_Reader::~Mem_File_Reader() +{ + if ( m_ownedPtr ) + free( const_cast( m_begin ) ); // see gz_compress for the malloc +} +#endif + +long Mem_File_Reader::size() const { return m_size; } long Mem_File_Reader::read_avail( void* p, long s ) { long r = remain(); - if ( s > r ) + if ( s > r || s < 0 ) s = r; - memcpy( p, begin + pos, s ); - pos += s; + memcpy( p, m_begin + m_pos, static_cast(s) ); + m_pos += s; return s; } -long Mem_File_Reader::tell() const { return pos; } +long Mem_File_Reader::tell() const { return m_pos; } blargg_err_t Mem_File_Reader::seek( long n ) { - if ( n > size_ ) + RETURN_VALIDITY_CHECK( n >= 0 ); + if ( n > m_size ) return eof_error; - pos = n; + m_pos = n; return 0; } +#ifdef HAVE_ZLIB_H + +bool Mem_File_Reader::gz_decompress() +{ + if ( m_size >= 2 && memcmp(m_begin, gz_magic, 2) != 0 ) + { + /* Don't try to decompress non-GZ files, just assign input pointer */ + return false; + } + + using vec_size = size_t; + const vec_size full_length = static_cast( m_size ); + const vec_size half_length = static_cast( m_size / 2 ); + + // We use malloc/friends here so we can realloc to grow buffer if needed + char *raw_data = reinterpret_cast ( malloc( full_length ) ); + size_t raw_data_size = full_length; + if ( !raw_data ) + return false; + + z_stream strm; + strm.next_in = const_cast( reinterpret_cast( m_begin ) ); + strm.avail_in = static_cast( m_size ); + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + + bool done = false; + + // Adding 16 sets bit 4, which enables zlib to auto-detect the + // header. + if ( inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK ) + { + free( raw_data ); + return false; + } + + while ( !done ) + { + /* If our output buffer is too small */ + if ( strm.total_out >= raw_data_size ) + { + raw_data_size += half_length; + raw_data = reinterpret_cast( realloc( raw_data, raw_data_size ) ); + if ( !raw_data ) { + return false; + } + } + + strm.next_out = reinterpret_cast( raw_data + strm.total_out ); + strm.avail_out = static_cast( static_cast( raw_data_size ) - strm.total_out ); + + /* Inflate another chunk. */ + int err = inflate( &strm, Z_SYNC_FLUSH ); + if ( err == Z_STREAM_END ) + done = true; + else if ( err != Z_OK ) + break; + } + + if ( inflateEnd(&strm) != Z_OK ) + { + free( raw_data ); + return false; + } + + m_begin = raw_data; + m_size = static_cast( strm.total_out ); + + return true; +} + +#endif /* HAVE_ZLIB_H */ + + // Callback_Reader Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) : callback( c ), data( d ) { - remain_ = size; + remain_ = max( 0l, size ); } long Callback_Reader::remain() const { return remain_; } @@ -175,34 +286,82 @@ long Callback_Reader::read_avail( void* out, long count ) { if ( count > remain_ ) count = remain_; - if ( Callback_Reader::read( out, count ) ) + if ( count < 0 || Callback_Reader::read( out, count ) ) count = -1; return count; } blargg_err_t Callback_Reader::read( void* out, long count ) { + RETURN_VALIDITY_CHECK( count >= 0 ); if ( count > remain_ ) return eof_error; - return callback( data, out, count ); + return callback( data, out, (int) count ); } // Std_File_Reader -Std_File_Reader::Std_File_Reader() : file_( 0 ) { } +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + +static const char* get_gzip_eof( const char* path, long* eof ) +{ + FILE* file = fopen( path, "rb" ); + if ( !file ) + return "Couldn't open file"; + + unsigned char buf [4]; + bool found_eof = false; + if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B ) + { + fseek( file, -4, SEEK_END ); + if ( fread( buf, 4, 1, file ) > 0 ) { + *eof = get_le32( buf ); + found_eof = true; + } + } + if ( !found_eof ) + { + fseek( file, 0, SEEK_END ); + *eof = ftell( file ); + } + const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : nullptr; + fclose( file ); + return err; +} +#endif + + +Std_File_Reader::Std_File_Reader() : + file_( nullptr ) +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + , size_( 0 ) +#endif +{ } Std_File_Reader::~Std_File_Reader() { close(); } blargg_err_t Std_File_Reader::open( const char* path ) { +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + // zlib transparently handles uncompressed data if magic header + // not present but we still need to grab size + RETURN_ERR( get_gzip_eof( path, &size_ ) ); + file_ = gzopen( path, "rb" ); +#else file_ = fopen( path, "rb" ); +#endif + if ( !file_ ) return "Couldn't open file"; - return 0; + return nullptr; } long Std_File_Reader::size() const { +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + if ( file_ ) + return size_; // Set for both compressed and uncompressed modes +#endif long pos = tell(); fseek( (FILE*) file_, 0, SEEK_END ); long result = tell(); @@ -212,24 +371,64 @@ long Std_File_Reader::size() const long Std_File_Reader::read_avail( void* p, long s ) { - return (long)fread( p, 1, s, (FILE*) file_ ); +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + if ( file_ && s > 0 && s <= UINT_MAX ) { + return gzread( reinterpret_cast(file_), + p, static_cast(s) ); + } + return 0l; +#else + const size_t readLength = static_cast( max( 0l, s ) ); + const auto result = fread( p, 1, readLength, reinterpret_cast(file_) ); + return static_cast( result ); +#endif /* HAVE_ZLIB_H */ } blargg_err_t Std_File_Reader::read( void* p, long s ) { - if ( s == (long) fread( p, 1, s, (FILE*) file_ ) ) + RETURN_VALIDITY_CHECK( s > 0 && s <= UINT_MAX ); +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + if ( file_ ) + { + const auto &gzfile = reinterpret_cast( file_ ); + if ( s == gzread( gzfile, p, static_cast( s ) ) ) + return nullptr; + if ( gzeof( gzfile ) ) + return eof_error; + return "Couldn't read from GZ file"; + } +#endif + const auto &file = reinterpret_cast( file_ ); + if ( s == static_cast( fread( p, 1, static_cast(s), file ) ) ) return 0; - if ( feof( (FILE*) file_ ) ) + if ( feof( file ) ) return eof_error; return "Couldn't read from file"; } -long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); } +long Std_File_Reader::tell() const +{ +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + if ( file_ ) + return gztell( reinterpret_cast( file_ ) ); +#endif + return ftell( reinterpret_cast( file_ ) ); +} blargg_err_t Std_File_Reader::seek( long n ) { - if ( !fseek( (FILE*) file_, n, SEEK_SET ) ) - return 0; +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + if ( file_ ) + { + if ( gzseek( reinterpret_cast( file_ ), n, SEEK_SET ) >= 0 ) + return nullptr; + if ( n > size_ ) + return eof_error; + return "Error seeking in GZ file"; + } +#endif + if ( !fseek( reinterpret_cast( file_ ), n, SEEK_SET ) ) + return nullptr; if ( n > size() ) return eof_error; return "Error seeking in file"; @@ -239,79 +438,12 @@ void Std_File_Reader::close() { if ( file_ ) { - fclose( (FILE*) file_ ); - file_ = 0; - } -} - -// Gzip_File_Reader - -#ifdef HAVE_ZLIB_H - -#include "zlib.h" - -static const char* get_gzip_eof( const char* path, long* eof ) -{ - FILE* file = fopen( path, "rb" ); - if ( !file ) - return "Couldn't open file"; - - unsigned char buf [4]; - if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B ) - { - fseek( file, -4, SEEK_END ); - fread( buf, 4, 1, file ); - *eof = get_le32( buf ); - } - else - { - fseek( file, 0, SEEK_END ); - *eof = ftell( file ); - } - const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0; - fclose( file ); - return err; -} - -Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { } - -Gzip_File_Reader::~Gzip_File_Reader() { close(); } - -blargg_err_t Gzip_File_Reader::open( const char* path ) -{ - close(); - - RETURN_ERR( get_gzip_eof( path, &size_ ) ); - - file_ = gzopen( path, "rb" ); - if ( !file_ ) - return "Couldn't open file"; - - return 0; -} - -long Gzip_File_Reader::size() const { return size_; } - -long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); } - -long Gzip_File_Reader::tell() const { return gztell( file_ ); } - -blargg_err_t Gzip_File_Reader::seek( long n ) -{ - if ( gzseek( file_, n, SEEK_SET ) >= 0 ) - return 0; - if ( n > size_ ) - return eof_error; - return "Error seeking in file"; -} - -void Gzip_File_Reader::close() -{ - if ( file_ ) - { - gzclose( file_ ); - file_ = 0; - } -} - +#if 0//[ZDOOM:unneeded]def HAVE_ZLIB_H + gzclose( reinterpret_cast( file_ ) ); +#else + fclose( reinterpret_cast( file_ ) ); #endif + file_ = nullptr; + } +} + diff --git a/game-music-emu/gme/Data_Reader.h b/game-music-emu/gme/Data_Reader.h index 6c22b678e..59357767e 100644 --- a/game-music-emu/gme/Data_Reader.h +++ b/game-music-emu/gme/Data_Reader.h @@ -6,6 +6,10 @@ #include "blargg_common.h" +#ifdef HAVE_ZLIB_H +#include +#endif + // Supports reading and finding out how many bytes are remaining class Data_Reader { public: @@ -65,25 +69,39 @@ public: long tell() const; blargg_err_t seek( long ); private: - void* file_; + void* file_; // Either FILE* or zlib's gzFile +#if 0//[ZDOOM:unneeded] def HAVE_ZLIB_H + long size_; // TODO: Fix ABI compat +#endif /* HAVE_ZLIB_H */ }; // Treats range of memory as a file class Mem_File_Reader : public File_Reader { public: Mem_File_Reader( const void*, long size ); - +#ifdef HAVE_ZLIB_H + ~Mem_File_Reader( ); +#endif /* HAVE_ZLIB_H */ + public: long size() const; long read_avail( void*, long ); long tell() const; blargg_err_t seek( long ); private: - const char* const begin; - const long size_; - long pos; +#ifdef HAVE_ZLIB_H + bool gz_decompress(); +#endif /* HAVE_ZLIB_H */ + + const char* m_begin; + long m_size; + long m_pos; +#ifdef HAVE_ZLIB_H + bool m_ownedPtr = false; // set if we must free m_begin +#endif /* HAVE_ZLIB_H */ }; + // Makes it look like there are only count bytes remaining class Subset_Reader : public Data_Reader { public: @@ -128,26 +146,4 @@ private: long remain_; }; -#ifdef HAVE_ZLIB_H -#include - -// Gzip compressed file reader -class Gzip_File_Reader : public File_Reader { -public: - blargg_err_t open( const char* path ); - void close(); - -public: - Gzip_File_Reader(); - ~Gzip_File_Reader(); - long size() const; - long read_avail( void*, long ); - long tell() const; - blargg_err_t seek( long ); -private: - gzFile file_; - long size_; -}; -#endif - #endif diff --git a/game-music-emu/gme/Dual_Resampler.cpp b/game-music-emu/gme/Dual_Resampler.cpp index 0e7828530..e774d85f8 100644 --- a/game-music-emu/gme/Dual_Resampler.cpp +++ b/game-music-emu/gme/Dual_Resampler.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Dual_Resampler.h" @@ -18,7 +18,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" -//unsigned const resampler_extra = 256; +unsigned const resampler_extra = 256; Dual_Resampler::Dual_Resampler() : sample_buf_size(0), @@ -68,10 +68,13 @@ void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, dsample_t* out ) assert( blip_buf.samples_avail() == pair_count ); resampler.write( new_count ); - + +#ifdef NDEBUG // Avoid warning when asserts are disabled + resampler.read( sample_buf.begin(), sample_buf_size ); +#else long count = resampler.read( sample_buf.begin(), sample_buf_size ); assert( count == (long) sample_buf_size ); - (void)count; // Silence warning in non-debug build +#endif mix_samples( blip_buf, out ); blip_buf.remove_samples( pair_count ); @@ -119,17 +122,17 @@ void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, dsample_t* out ) { int s = sn.read(); blargg_long l = (blargg_long) in [0] * 2 + s; - if ( (BOOST::int16_t) l != l ) + if ( (int16_t) l != l ) l = 0x7FFF - (l >> 24); sn.next( bass ); blargg_long r = (blargg_long) in [1] * 2 + s; - if ( (BOOST::int16_t) r != r ) + if ( (int16_t) r != r ) r = 0x7FFF - (r >> 24); in += 2; - out [0] = (dsample_t)l; - out [1] = (dsample_t)r; + out [0] = l; + out [1] = r; out += 2; } diff --git a/game-music-emu/gme/Dual_Resampler.h b/game-music-emu/gme/Dual_Resampler.h index 6dc8dcfc2..512fd97d0 100644 --- a/game-music-emu/gme/Dual_Resampler.h +++ b/game-music-emu/gme/Dual_Resampler.h @@ -1,6 +1,6 @@ // Combination of Fir_Resampler and Blip_Buffer mixing. Used by Sega FM emulators. -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef DUAL_RESAMPLER_H #define DUAL_RESAMPLER_H diff --git a/game-music-emu/gme/Effects_Buffer.cpp b/game-music-emu/gme/Effects_Buffer.cpp index 6af9c14be..56b0c5b5c 100644 --- a/game-music-emu/gme/Effects_Buffer.cpp +++ b/game-music-emu/gme/Effects_Buffer.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Effects_Buffer.h" @@ -63,29 +63,49 @@ void Effects_Buffer::set_depth( double d ) config( c ); } -Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 ) +Effects_Buffer::Effects_Buffer( int num_voices, bool center_only ) + : Multi_Buffer( 2*num_voices ) + , max_voices(num_voices) + , bufs(max_voices * (center_only ? (max_buf_count - 4) : max_buf_count)) + , chan_types(max_voices * chan_types_count) + , stereo_remain(0) + , effect_remain(0) + // TODO: Reorder buf_count to be initialized before bufs to factor out channel sizing + , buf_count(max_voices * (center_only ? (max_buf_count - 4) : max_buf_count)) + , effects_enabled(false) + , reverb_buf(max_voices, std::vector(reverb_size)) + , echo_buf(max_voices, std::vector(echo_size)) + , reverb_pos(max_voices) + , echo_pos(max_voices) { - buf_count = center_only ? max_buf_count - 4 : max_buf_count; - - echo_pos = 0; - reverb_pos = 0; - - stereo_remain = 0; - effect_remain = 0; - effects_enabled = false; set_depth( 0 ); } -Effects_Buffer::~Effects_Buffer() { } +Effects_Buffer::~Effects_Buffer() +{} blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec ) { - if ( !echo_buf.size() ) - RETURN_ERR( echo_buf.resize( echo_size ) ); - - if ( !reverb_buf.size() ) - RETURN_ERR( reverb_buf.resize( reverb_size ) ); - + try + { + for(int i=0; i 2 ) - out = 2; + if ( out > chan_types_count-1 ) + out = chan_types_count-1; } else if ( !(type & noise_type) && (type & type_index_mask) % 3 != 0 ) { out = type & 1; } - return chan_types [out]; + return chan_types [(i%max_voices)*chan_types_count+out]; } void Effects_Buffer::end_frame( blip_time_t clock_count ) { int bufs_used = 0; - for ( int i = 0; i < buf_count; i++ ) - { - bufs_used |= bufs [i].clear_modified() << i; - bufs [i].end_frame( clock_count ); - } - int stereo_mask = (config_.effects_enabled ? 0x78 : 0x06); - if ( (bufs_used & stereo_mask) && buf_count == max_buf_count ) - stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); - - if ( effects_enabled || config_.effects_enabled ) - effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + const int buf_count_per_voice = buf_count/max_voices; + for ( int v = 0; v < max_voices; v++ ) // foreach voice + { + for ( int i = 0; i < buf_count_per_voice; i++) // foreach buffer of that voice + { + bufs_used |= bufs [v*buf_count_per_voice + i].clear_modified() << i; + bufs [v*buf_count_per_voice + i].end_frame( clock_count ); + + if ( (bufs_used & stereo_mask) && buf_count == max_voices*max_buf_count ) + stereo_remain = max(stereo_remain, bufs [v*buf_count_per_voice + i].samples_avail() + bufs [v*buf_count_per_voice + i].output_latency()); + if ( effects_enabled || config_.effects_enabled ) + effect_remain = max(effect_remain, bufs [v*buf_count_per_voice + i].samples_avail() + bufs [v*buf_count_per_voice + i].output_latency()); + } + bufs_used = 0; + } effects_enabled = config_.effects_enabled; } @@ -248,15 +291,17 @@ long Effects_Buffer::samples_avail() const long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) { - require( total_samples % 2 == 0 ); // count must be even - + const int n_channels = max_voices * 2; + const int buf_count_per_voice = buf_count/max_voices; + + require( total_samples % n_channels == 0 ); // as many items needed to fill at least one frame + long remain = bufs [0].samples_avail(); - if ( remain > (total_samples >> 1) ) - remain = (total_samples >> 1); - total_samples = remain; + total_samples = remain = min( remain, total_samples/n_channels ); + while ( remain ) { - int active_bufs = buf_count; + int active_bufs = buf_count_per_voice; long count = remain; // optimizing mixing to skip any channels which had nothing added @@ -286,7 +331,7 @@ long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) active_bufs = 1; } - out += count * 2; + out += count * n_channels; remain -= count; stereo_remain -= count; @@ -297,23 +342,31 @@ long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) if ( effect_remain < 0 ) effect_remain = 0; - for ( int i = 0; i < buf_count; i++ ) + // skip the output from any buffers that didn't contribute to the sound output + // during this frame (e.g. if we only render mono then only the very first buf + // is 'active') + for ( int v = 0; v < max_voices; v++ ) // foreach voice { - if ( i < active_bufs ) - bufs [i].remove_samples( count ); - else - bufs [i].remove_silence( count ); // keep time synchronized + for ( int i = 0; i < buf_count_per_voice; i++) // foreach buffer of that voice + { + if ( i < active_bufs ) + bufs [v*buf_count_per_voice + i].remove_samples( count ); + else // keep time synchronized + bufs [v*buf_count_per_voice + i].remove_silence( count ); + } } } - return total_samples * 2; + return total_samples * n_channels; } void Effects_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) { + for(int i=0; i> 1; n; --n ) @@ -324,41 +377,45 @@ void Effects_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) blargg_long cs1 = BLIP_READER_READ( c ); BLIP_READER_NEXT( c, bass ); - if ( (BOOST::int16_t) cs0 != cs0 ) + if ( (int16_t) cs0 != cs0 ) cs0 = 0x7FFF - (cs0 >> 24); - ((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16); + ((uint32_t*) out) [i*2+0] = ((uint16_t) cs0) | (uint16_t(cs0) << 16); - if ( (BOOST::int16_t) cs1 != cs1 ) + if ( (int16_t) cs1 != cs1 ) cs1 = 0x7FFF - (cs1 >> 24); - ((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16); - out += 4; + ((uint32_t*) out) [i*2+1] = ((uint16_t) cs1) | (uint16_t(cs1) << 16); + out += max_voices*4; } if ( count & 1 ) { int s = BLIP_READER_READ( c ); BLIP_READER_NEXT( c, bass ); - out [0] = s; - out [1] = s; - if ( (BOOST::int16_t) s != s ) + out [i*2+0] = s; + out [i*2+1] = s; + if ( (int16_t) s != s ) { s = 0x7FFF - (s >> 24); - out [0] = s; - out [1] = s; + out [i*2+0] = s; + out [i*2+1] = s; } } - BLIP_READER_END( c, bufs [0] ); + BLIP_READER_END( c, bufs [i*max_buf_count+0] ); + } } -void Effects_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count ) +void Effects_Buffer::mix_stereo( blip_sample_t* out_, blargg_long frames ) { + for(int i=0; i> 24); - out [0] = left; - out [1] = right; + if ( (int16_t) right != right ) + right = 0x7FFF - (right >> 24); + + out [i*2+0] = left; + out [i*2+1] = right; - out += 2; + out += max_voices*2; - if ( (BOOST::int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); } - BLIP_READER_END( r, bufs [2] ); - BLIP_READER_END( l, bufs [1] ); - BLIP_READER_END( c, bufs [0] ); + BLIP_READER_END( r, bufs [i*max_buf_count+2] ); + BLIP_READER_END( l, bufs [i*max_buf_count+1] ); + BLIP_READER_END( c, bufs [i*max_buf_count+0] ); + } } -void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out_, blargg_long count ) +void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out_, blargg_long frames ) { + for(int i=0; ireverb_buf.begin(); - blip_sample_t* const echo_buf = this->echo_buf.begin(); - int echo_pos = this->echo_pos; - int reverb_pos = this->reverb_pos; + blip_sample_t* const reverb_buf = &this->reverb_buf[i][0]; + blip_sample_t* const echo_buf = &this->echo_buf[i][0]; + int echo_pos = this->echo_pos[i]; + int reverb_pos = this->reverb_pos[i]; + int count = frames; while ( count-- ) { int sum1_s = BLIP_READER_READ( sq1 ); @@ -430,42 +492,45 @@ void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out_, blargg_long count ) echo_buf [echo_pos] = sum3_s; echo_pos = (echo_pos + 1) & echo_mask; - if ( (BOOST::int16_t) left != left ) + if ( (int16_t) left != left ) left = 0x7FFF - (left >> 24); - out [0] = left; - out [1] = right; - - out += 2; - - if ( (BOOST::int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); + if ( (int16_t) right != right ) + right = 0x7FFF - (right >> 24); + + out [i*2+0] = left; + out [i*2+1] = right; + out += max_voices*2; } - this->reverb_pos = reverb_pos; - this->echo_pos = echo_pos; + this->reverb_pos[i] = reverb_pos; + this->echo_pos[i] = echo_pos; - BLIP_READER_END( sq1, bufs [0] ); - BLIP_READER_END( sq2, bufs [1] ); - BLIP_READER_END( center, bufs [2] ); + BLIP_READER_END( sq1, bufs [i*max_buf_count+0] ); + BLIP_READER_END( sq2, bufs [i*max_buf_count+1] ); + BLIP_READER_END( center, bufs [i*max_buf_count+2] ); + } } -void Effects_Buffer::mix_enhanced( blip_sample_t* out_, blargg_long count ) +void Effects_Buffer::mix_enhanced( blip_sample_t* out_, blargg_long frames ) { + for(int i=0; ireverb_buf.begin(); - blip_sample_t* const echo_buf = this->echo_buf.begin(); - int echo_pos = this->echo_pos; - int reverb_pos = this->reverb_pos; + blip_sample_t* const reverb_buf = &this->reverb_buf[i][0]; + blip_sample_t* const echo_buf = &this->echo_buf[i][0]; + int echo_pos = this->echo_pos[i]; + int reverb_pos = this->reverb_pos[i]; + int count = frames; while ( count-- ) { int sum1_s = BLIP_READER_READ( sq1 ); @@ -504,26 +569,27 @@ void Effects_Buffer::mix_enhanced( blip_sample_t* out_, blargg_long count ) echo_buf [echo_pos] = sum3_s; echo_pos = (echo_pos + 1) & echo_mask; - if ( (BOOST::int16_t) left != left ) + if ( (int16_t) left != left ) left = 0x7FFF - (left >> 24); - out [0] = left; - out [1] = right; - - out += 2; - - if ( (BOOST::int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); + if ( (int16_t) right != right ) + right = 0x7FFF - (right >> 24); + + out [i*2+0] = left; + out [i*2+1] = right; + + out += max_voices*2; } - this->reverb_pos = reverb_pos; - this->echo_pos = echo_pos; + this->reverb_pos[i] = reverb_pos; + this->echo_pos[i] = echo_pos; - BLIP_READER_END( l1, bufs [3] ); - BLIP_READER_END( r1, bufs [4] ); - BLIP_READER_END( l2, bufs [5] ); - BLIP_READER_END( r2, bufs [6] ); - BLIP_READER_END( sq1, bufs [0] ); - BLIP_READER_END( sq2, bufs [1] ); - BLIP_READER_END( center, bufs [2] ); + BLIP_READER_END( l1, bufs [i*max_buf_count+3] ); + BLIP_READER_END( r1, bufs [i*max_buf_count+4] ); + BLIP_READER_END( l2, bufs [i*max_buf_count+5] ); + BLIP_READER_END( r2, bufs [i*max_buf_count+6] ); + BLIP_READER_END( sq1, bufs [i*max_buf_count+0] ); + BLIP_READER_END( sq2, bufs [i*max_buf_count+1] ); + BLIP_READER_END( center, bufs [i*max_buf_count+2] ); + } } diff --git a/game-music-emu/gme/Effects_Buffer.h b/game-music-emu/gme/Effects_Buffer.h index 832c44b06..ec634d622 100644 --- a/game-music-emu/gme/Effects_Buffer.h +++ b/game-music-emu/gme/Effects_Buffer.h @@ -1,17 +1,21 @@ // Multi-channel effects buffer with panning, echo and reverb -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef EFFECTS_BUFFER_H #define EFFECTS_BUFFER_H #include "Multi_Buffer.h" +#include + // Effects_Buffer uses several buffers and outputs stereo sample pairs. class Effects_Buffer : public Multi_Buffer { public: + // nVoices indicates the number of voices for which buffers will be allocated + // to make Effects_Buffer work as "mix everything to one", nVoices will be 1 // If center_only is true, only center buffers are created and // less memory is used. - Effects_Buffer( bool center_only = false ); + Effects_Buffer( int nVoices = 1, bool center_only = false ); // Channel Effect Center Pan // --------------------------------- @@ -50,21 +54,21 @@ public: long samples_avail() const; private: typedef long fixed_t; - + int max_voices; enum { max_buf_count = 7 }; - Blip_Buffer bufs [max_buf_count]; + std::vector bufs; enum { chan_types_count = 3 }; - channel_t chan_types [3]; + std::vector chan_types; config_t config_; long stereo_remain; long effect_remain; int buf_count; bool effects_enabled; - blargg_vector reverb_buf; - blargg_vector echo_buf; - int reverb_pos; - int echo_pos; + std::vector > reverb_buf; + std::vector > echo_buf; + std::vector reverb_pos; + std::vector echo_pos; struct { fixed_t pan_1_levels [2]; diff --git a/game-music-emu/gme/Fir_Resampler.cpp b/game-music-emu/gme/Fir_Resampler.cpp index a311895a2..d8dd6837c 100644 --- a/game-music-emu/gme/Fir_Resampler.cpp +++ b/game-music-emu/gme/Fir_Resampler.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Fir_Resampler.h" @@ -23,10 +23,6 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #undef PI #define PI 3.1415926535897932384626433832795029 -#if _MSC_VER >= 1911 -#pragma float_control(precise, on, push) -#endif // _MSC_VER >= 1911 - static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale, int count, short* out ) { @@ -56,10 +52,6 @@ static void gen_sinc( double rolloff, int width, double offset, double spacing, } } -#if _MSC_VER >= 1911 -#pragma float_control(pop) -#endif // _MSC_VER >= 1911 - Fir_Resampler_::Fir_Resampler_( int width, sample_t* impulses_ ) : width_( width ), write_offset( width * stereo - stereo ), @@ -164,7 +156,7 @@ int Fir_Resampler_::input_needed( blargg_long output_count ) const output_count -= 2; } - long input_extra = (long)(input_count - (write_pos - &buf [(width_ - 1) * stereo])); + long input_extra = input_count - (write_pos - &buf [(width_ - 1) * stereo]); if ( input_extra < 0 ) input_extra = 0; return input_extra; @@ -194,8 +186,8 @@ int Fir_Resampler_::avail_( blargg_long input_count ) const int Fir_Resampler_::skip_input( long count ) { - int remain = int(write_pos - buf.begin()); - int max_count = int(remain - width_ * stereo); + int remain = write_pos - buf.begin(); + int max_count = remain - width_ * stereo; if ( count > max_count ) count = max_count; diff --git a/game-music-emu/gme/Fir_Resampler.h b/game-music-emu/gme/Fir_Resampler.h index 6cd1c218d..d637ec41c 100644 --- a/game-music-emu/gme/Fir_Resampler.h +++ b/game-music-emu/gme/Fir_Resampler.h @@ -1,6 +1,6 @@ // Finite impulse response (FIR) resampler with adjustable FIR size -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef FIR_RESAMPLER_H #define FIR_RESAMPLER_H @@ -31,7 +31,7 @@ public: void clear(); // Number of input samples that can be written - int max_write() const { return int(buf.end() - write_pos); } + int max_write() const { return buf.end() - write_pos; } // Pointer to place to write input samples sample_t* buffer() { return write_pos; } @@ -40,7 +40,7 @@ public: void write( long count ); // Number of input samples in buffer - int written() const { return int(write_pos - &buf [write_offset]); } + int written() const { return write_pos - &buf [write_offset]; } // Skip 'count' input samples. Returns number of samples actually skipped. int skip_input( long count ); @@ -51,7 +51,7 @@ public: int input_needed( blargg_long count ) const; // Number of output samples available - int avail() const { return avail_( blargg_long(write_pos - &buf [width_ * stereo] )); } + int avail() const { return avail_( write_pos - &buf [width_ * stereo] ); } public: ~Fir_Resampler_(); @@ -161,11 +161,11 @@ int Fir_Resampler::read( sample_t* out_begin, blargg_long count ) imp_phase = res - remain; - int left = int(write_pos - in); + int left = write_pos - in; write_pos = &buf [left]; memmove( buf.begin(), in, left * sizeof *in ); - return int(out - out_begin); + return out - out_begin; } #endif diff --git a/game-music-emu/gme/Gb_Apu.cpp b/game-music-emu/gme/Gb_Apu.cpp index 866594ddf..82a9cc1b6 100644 --- a/game-music-emu/gme/Gb_Apu.cpp +++ b/game-music-emu/gme/Gb_Apu.cpp @@ -123,7 +123,7 @@ void Gb_Apu::reset() 0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table 0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA }; - memcpy( wave.wave, initial_wave, sizeof wave.wave ); + memcpy( wave.wave, initial_wave, sizeof initial_wave ); } void Gb_Apu::run_until( blip_time_t end_time ) diff --git a/game-music-emu/gme/Gb_Apu.h b/game-music-emu/gme/Gb_Apu.h index e74ebc55b..9b251262f 100644 --- a/game-music-emu/gme/Gb_Apu.h +++ b/game-music-emu/gme/Gb_Apu.h @@ -68,7 +68,7 @@ private: Gb_Square square2; Gb_Wave wave; Gb_Noise noise; - BOOST::uint8_t regs [register_count]; + uint8_t regs [register_count]; Gb_Square::Synth square_synth; // used by squares Gb_Wave::Synth other_synth; // used by wave and noise diff --git a/game-music-emu/gme/Gb_Cpu.cpp b/game-music-emu/gme/Gb_Cpu.cpp index bfae186e9..db1abee58 100644 --- a/game-music-emu/gme/Gb_Cpu.cpp +++ b/game-music-emu/gme/Gb_Cpu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Gb_Cpu.h" @@ -89,12 +89,6 @@ unsigned const n_flag = 0x40; unsigned const h_flag = 0x20; unsigned const c_flag = 0x10; -#ifdef _MSC_VER -// warning C4101: 'blargg_failed_' : unreferenced local variable -// -- produced by the BOOST_STATIC_ASSERT line below -#pragma warning(disable:4101) -#endif - bool Gb_Cpu::run( blargg_long cycle_count ) { state_.remain = blargg_ulong (cycle_count + clocks_per_instr) / clocks_per_instr; @@ -102,8 +96,6 @@ bool Gb_Cpu::run( blargg_long cycle_count ) this->state = &s; memcpy( &s, &this->state_, sizeof s ); - typedef BOOST::uint16_t uint16_t; - #if BLARGG_BIG_ENDIAN #define R8( n ) (r8_ [n]) #elif BLARGG_LITTLE_ENDIAN @@ -116,11 +108,11 @@ bool Gb_Cpu::run( blargg_long cycle_count ) core_regs_t rg; // individual registers struct { - BOOST::uint16_t bc, de, hl, unused; // pairs + uint16_t bc, de, hl, unused; // pairs } rp; uint8_t r8_ [8]; // indexed registers (use R8 macro due to endian dependence) - BOOST::uint16_t r16 [4]; // indexed pairs + uint16_t r16 [4]; // indexed pairs }; BOOST_STATIC_ASSERT( sizeof rg == 8 && sizeof rp == 8 ); @@ -168,7 +160,7 @@ loop: #define BRANCH( cond )\ {\ pc++;\ - int offset = (BOOST::int8_t) data;\ + int offset = (int8_t) data;\ if ( !(cond) ) goto loop;\ pc = uint16_t (pc + offset);\ goto loop;\ @@ -688,7 +680,7 @@ loop: unsigned prev; case 0xF8: // LD HL,SP+imm - temp = BOOST::int8_t (data); // sign-extend to 16 bits + temp = int8_t (data); // sign-extend to 16 bits pc++; flags = 0; temp += sp; @@ -696,7 +688,7 @@ loop: goto add_16_hl; case 0xE8: // ADD SP,IMM - temp = BOOST::int8_t (data); // sign-extend to 16 bits + temp = int8_t (data); // sign-extend to 16 bits pc++; flags = 0; temp += sp; @@ -717,7 +709,7 @@ loop: temp += prev; flags &= z_flag; add_16_hl: - rp.hl = (uint16_t)temp; + rp.hl = temp; add_16_comm: flags |= (temp >> 12) & c_flag; flags |= (((temp & 0x0FFF) - (prev & 0x0FFF)) >> 7) & h_flag; diff --git a/game-music-emu/gme/Gb_Cpu.h b/game-music-emu/gme/Gb_Cpu.h index 0994cec29..d3df30cac 100644 --- a/game-music-emu/gme/Gb_Cpu.h +++ b/game-music-emu/gme/Gb_Cpu.h @@ -1,7 +1,7 @@ // Nintendo Game Boy CPU emulator // Treats every instruction as taking 4 cycles -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef GB_CPU_H #define GB_CPU_H @@ -13,8 +13,6 @@ typedef unsigned gb_addr_t; // 16-bit CPU address class Gb_Cpu { enum { clocks_per_instr = 4 }; public: - typedef BOOST::uint8_t uint8_t; - // Clear registers and map all pages to unmapped void reset( void* unmapped = 0 ); @@ -39,7 +37,7 @@ public: struct registers_t : core_regs_t { long pc; // more than 16 bits to allow overflow detection - BOOST::uint16_t sp; + uint16_t sp; }; registers_t r; @@ -81,7 +79,7 @@ private: void set_code_page( int, uint8_t* ); }; -inline BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) +inline uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) { return state->code_map [addr >> page_shift] + addr #if !BLARGG_NONPORTABLE diff --git a/game-music-emu/gme/Gb_Oscs.h b/game-music-emu/gme/Gb_Oscs.h index d7f88ea14..8cb026c3e 100644 --- a/game-music-emu/gme/Gb_Oscs.h +++ b/game-music-emu/gme/Gb_Oscs.h @@ -15,7 +15,7 @@ struct Gb_Osc Blip_Buffer* outputs [4]; // NULL, right, left, center Blip_Buffer* output; int output_select; - BOOST::uint8_t* regs; // osc's 5 registers + uint8_t* regs; // osc's 5 registers int delay; int last_amp; @@ -68,7 +68,7 @@ struct Gb_Wave : Gb_Osc Synth const* synth; int wave_pos; enum { wave_size = 32 }; - BOOST::uint8_t wave [wave_size]; + uint8_t wave [wave_size]; void write_register( int, int ); void run( blip_time_t, blip_time_t, int playing ); diff --git a/game-music-emu/gme/Gbs_Emu.cpp b/game-music-emu/gme/Gbs_Emu.cpp index 05a0b9935..6c5def339 100644 --- a/game-music-emu/gme/Gbs_Emu.cpp +++ b/game-music-emu/gme/Gbs_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Gbs_Emu.h" @@ -101,7 +101,7 @@ static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; } static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; } static gme_type_t_ const gme_gbs_type_ = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }; -gme_type_t const gme_gbs_type = &gme_gbs_type_; +BLARGG_EXPORT extern gme_type_t const gme_gbs_type = &gme_gbs_type_; // Setup @@ -175,7 +175,7 @@ void Gbs_Emu::update_timer() play_period = blip_time_t (play_period / tempo()); } -static BOOST::uint8_t const sound_data [Gb_Apu::register_count] = { +static uint8_t const sound_data [Gb_Apu::register_count] = { 0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1 0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2 0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave diff --git a/game-music-emu/gme/Gbs_Emu.h b/game-music-emu/gme/Gbs_Emu.h index b233a2b4f..580f395c6 100644 --- a/game-music-emu/gme/Gbs_Emu.h +++ b/game-music-emu/gme/Gbs_Emu.h @@ -1,6 +1,6 @@ // Nintendo Game Boy GBS music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef GBS_EMU_H #define GBS_EMU_H diff --git a/game-music-emu/gme/Gme_File.cpp b/game-music-emu/gme/Gme_File.cpp index 912a9adc1..a5e4516d6 100644 --- a/game-music-emu/gme/Gme_File.cpp +++ b/game-music-emu/gme/Gme_File.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Gme_File.h" @@ -60,8 +60,8 @@ blargg_err_t Gme_File::load_mem_( byte const* data, long size ) blargg_err_t Gme_File::load_( Data_Reader& in ) { RETURN_ERR( file_data.resize( in.remain() ) ); - RETURN_ERR( in.read( file_data.begin(), (long)file_data.size() ) ); - return load_mem_( file_data.begin(), (long)file_data.size() ); + RETURN_ERR( in.read( file_data.begin(), file_data.size() ) ); + return load_mem_( file_data.begin(), file_data.size() ); } // public load functions call this at beginning diff --git a/game-music-emu/gme/Gme_File.h b/game-music-emu/gme/Gme_File.h index 2d0a57344..3ec36bc8e 100644 --- a/game-music-emu/gme/Gme_File.h +++ b/game-music-emu/gme/Gme_File.h @@ -1,6 +1,6 @@ // Common interface to game music file loading and information -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef GME_FILE_H #define GME_FILE_H @@ -105,7 +105,7 @@ public: Gme_File(); virtual ~Gme_File(); BLARGG_DISABLE_NOTHROW - typedef BOOST::uint8_t byte; + typedef uint8_t byte; protected: // Services void set_track_count( int n ) { track_count_ = raw_track_count_ = n; } @@ -154,11 +154,7 @@ Music_Emu* gme_new_( Music_Emu*, long sample_rate ); { Gme_File::copy_field_( out->name, in.name, sizeof in.name ); } #ifndef GME_FILE_READER - #ifdef HAVE_ZLIB_H - #define GME_FILE_READER Gzip_File_Reader - #else - #define GME_FILE_READER Std_File_Reader - #endif + #define GME_FILE_READER Std_File_Reader #elif defined (GME_FILE_READER_INCLUDE) #include GME_FILE_READER_INCLUDE #endif diff --git a/game-music-emu/gme/Gym_Emu.cpp b/game-music-emu/gme/Gym_Emu.cpp index f149aebb5..bb99ff033 100644 --- a/game-music-emu/gme/Gym_Emu.cpp +++ b/game-music-emu/gme/Gym_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Gym_Emu.h" @@ -162,7 +162,7 @@ static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; } static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; } static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 }; -gme_type_t const gme_gym_type = &gme_gym_type_; +BLARGG_EXPORT extern gme_type_t const gme_gym_type = &gme_gym_type_; // Setup diff --git a/game-music-emu/gme/Gym_Emu.h b/game-music-emu/gme/Gym_Emu.h index 1e4ed8b3e..290f57f5c 100644 --- a/game-music-emu/gme/Gym_Emu.h +++ b/game-music-emu/gme/Gym_Emu.h @@ -1,7 +1,7 @@ // Sega Genesis/Mega Drive GYM music file emulator // Includes with PCM timing recovery to improve sample quality. -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef GYM_EMU_H #define GYM_EMU_H diff --git a/game-music-emu/gme/Hes_Apu.cpp b/game-music-emu/gme/Hes_Apu.cpp index 56b59dd77..1df811592 100644 --- a/game-music-emu/gme/Hes_Apu.cpp +++ b/game-music-emu/gme/Hes_Apu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Hes_Apu.h" @@ -106,10 +106,10 @@ void Hes_Osc::run_until( synth_t& synth_, blip_time_t end_time ) unsigned noise_lfsr = this->noise_lfsr; do { - int new_dac = 0x1F & (unsigned)-(int)(noise_lfsr >> 1 & 1); + int new_dac = 0x1F & -(noise_lfsr >> 1 & 1); // Implemented using "Galios configuration" // TODO: find correct LFSR algorithm - noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & (unsigned)-(int)(noise_lfsr & 1)); + noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & -(noise_lfsr & 1)); //noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1)); int delta = new_dac - dac; if ( delta ) diff --git a/game-music-emu/gme/Hes_Apu.h b/game-music-emu/gme/Hes_Apu.h index 89dde02cd..1efc0a064 100644 --- a/game-music-emu/gme/Hes_Apu.h +++ b/game-music-emu/gme/Hes_Apu.h @@ -1,6 +1,6 @@ // Turbo Grafx 16 (PC Engine) PSG sound chip emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef HES_APU_H #define HES_APU_H diff --git a/game-music-emu/gme/Hes_Cpu.cpp b/game-music-emu/gme/Hes_Cpu.cpp index 51aa4259f..d15145968 100644 --- a/game-music-emu/gme/Hes_Cpu.cpp +++ b/game-music-emu/gme/Hes_Cpu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Hes_Cpu.h" @@ -39,7 +39,7 @@ int const ram_addr = 0x2000; // status flags int const st_n = 0x80; int const st_v = 0x40; -//unused: int const st_t = 0x20; +int const st_t = 0x20; int const st_b = 0x10; int const st_d = 0x08; int const st_i = 0x04; @@ -87,12 +87,6 @@ void Hes_Cpu::set_mmr( int reg, int bank ) #define GET_SP() ((sp - 1) & 0xFF) #define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) -// even on x86, using short and unsigned char was slower -typedef int fint16; -typedef unsigned fuint16; -typedef unsigned fuint8; -typedef blargg_long fint32; - bool Hes_Cpu::run( hes_time_t end_time ) { bool illegal_encountered = false; @@ -100,14 +94,14 @@ bool Hes_Cpu::run( hes_time_t end_time ) state_t s = this->state_; this->state = &s; // even on x86, using s.time in place of s_time was slower - fint16 s_time = s.time; + int16_t s_time = s.time; // registers - fuint16 pc = r.pc; - fuint8 a = r.a; - fuint8 x = r.x; - fuint8 y = r.y; - fuint16 sp; + uint16_t pc = r.pc; + uint8_t a = r.a; + uint8_t x = r.x; + uint8_t y = r.y; + uint16_t sp; SET_SP( r.sp ); #define IS_NEG (nz & 0x8080) @@ -126,11 +120,11 @@ bool Hes_Cpu::run( hes_time_t end_time ) nz |= ~in & st_z;\ } while ( 0 ) - fuint8 status; - fuint16 c; // carry set if (c & 0x100) != 0 - fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + uint8_t status; + uint16_t c; // carry set if (c & 0x100) != 0 + uint16_t nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 { - fuint8 temp = r.status; + uint8_t temp = r.status; SET_STATUS( temp ); } @@ -159,7 +153,7 @@ loop: check( (unsigned) x < 0x100 ); uint8_t const* instr = s.code_map [pc >> page_shift]; - fuint8 opcode; + uint8_t opcode; // TODO: eliminate this special case #if BLARGG_NONPORTABLE @@ -193,7 +187,7 @@ loop: 4,7,7,17,2,4,6,7,2,5,4,2,2,5,7,6 // F }; // 0x00 was 8 - fuint16 data; + uint16_t data; data = clock_table [opcode]; if ( (s_time += data) >= 0 ) goto possibly_out_of_time; @@ -230,10 +224,10 @@ possibly_out_of_time: // TODO: more efficient way to handle negative branch that wraps PC around #define BRANCH( cond )\ {\ - fint16 offset = (BOOST::int8_t) data;\ + int16_t offset = (int8_t) data;\ pc++;\ if ( !(cond) ) goto branch_not_taken;\ - pc = BOOST::uint16_t (pc + offset);\ + pc = uint16_t (pc + offset);\ goto loop;\ } @@ -283,7 +277,7 @@ possibly_out_of_time: case 0xCF: case 0xDF: case 0xEF: { - fuint16 t = 0x101 * READ_LOW( data ); + uint16_t t = 0x101 * READ_LOW( data ); t ^= 0xFF; pc++; data = GET_MSB(); @@ -311,7 +305,7 @@ possibly_out_of_time: goto branch_taken; case 0x20: { // JSR - fuint16 temp = pc + 1; + uint16_t temp = pc + 1; pc = GET_ADDR(); WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); sp = (sp - 2) | 0x100; @@ -332,7 +326,7 @@ possibly_out_of_time: case 0xBD:{// LDA abs,X PAGE_CROSS_PENALTY( data + x ); - fuint16 addr = GET_ADDR() + x; + uint16_t addr = GET_ADDR() + x; pc += 2; CPU_READ_FAST( this, addr, TIME, nz ); a = nz; @@ -340,7 +334,7 @@ possibly_out_of_time: } case 0x9D:{// STA abs,X - fuint16 addr = GET_ADDR() + x; + uint16_t addr = GET_ADDR() + x; pc += 2; CPU_WRITE_FAST( this, addr, a, TIME ); goto loop; @@ -354,7 +348,7 @@ possibly_out_of_time: goto loop; case 0xAE:{// LDX abs - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; CPU_READ_FAST( this, addr, TIME, nz ); x = nz; @@ -369,7 +363,7 @@ possibly_out_of_time: // Load/store { - fuint16 addr; + uint16_t addr; case 0x91: // STA (ind),Y addr = 0x100 * READ_LOW( uint8_t (data + 1) ); addr += READ_LOW( data ) + y; @@ -395,7 +389,7 @@ possibly_out_of_time: } { - fuint16 addr; + uint16_t addr; case 0xA1: // LDA (ind,X) data = uint8_t (data + x); case 0xB2: // LDA (ind) @@ -425,7 +419,7 @@ possibly_out_of_time: case 0xBE:{// LDX abs,y PAGE_CROSS_PENALTY( data + y ); - fuint16 addr = GET_ADDR() + y; + uint16_t addr = GET_ADDR() + y; pc += 2; FLUSH_TIME(); x = nz = READ( addr ); @@ -449,7 +443,7 @@ possibly_out_of_time: case 0x3C: // BIT abs,x data += x; case 0x2C:{// BIT abs - fuint16 addr; + uint16_t addr; ADD_PAGE( addr ); FLUSH_TIME(); nz = READ( addr ); @@ -472,7 +466,7 @@ possibly_out_of_time: goto loop; { - fuint16 addr; + uint16_t addr; case 0xB3: // TST abs,x addr = GET_MSB() + x; @@ -505,7 +499,7 @@ possibly_out_of_time: goto loop; { - fuint16 addr; + uint16_t addr; case 0x0C: // TSB abs case 0x1C: // TRB abs addr = GET_ADDR(); @@ -610,7 +604,7 @@ possibly_out_of_time: data += x; PAGE_CROSS_PENALTY( data ); case 0xAC:{// LDY abs - fuint16 addr = data + 0x100 * GET_MSB(); + uint16_t addr = data + 0x100 * GET_MSB(); pc += 2; FLUSH_TIME(); y = nz = READ( addr ); @@ -619,7 +613,7 @@ possibly_out_of_time: } { - fuint8 temp; + uint8_t temp; case 0x8C: // STY abs temp = y; goto store_abs; @@ -627,7 +621,7 @@ possibly_out_of_time: case 0x8E: // STX abs temp = x; store_abs: - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; FLUSH_TIME(); WRITE( addr, temp ); @@ -638,7 +632,7 @@ possibly_out_of_time: // Compare case 0xEC:{// CPX abs - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc++; FLUSH_TIME(); data = READ( addr ); @@ -657,7 +651,7 @@ possibly_out_of_time: goto loop; case 0xCC:{// CPY abs - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc++; FLUSH_TIME(); data = READ( addr ); @@ -684,7 +678,7 @@ possibly_out_of_time: data = 0x100 * READ_LOW( uint8_t (data + 1) ) + READ_LOW( data );\ goto ptr##op;\ case op + 0x0C:{/* (ind),y */\ - fuint16 temp = READ_LOW( data ) + y;\ + uint16_t temp = READ_LOW( data ) + y;\ PAGE_CROSS_PENALTY( temp );\ data = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\ goto ptr##op;\ @@ -742,8 +736,8 @@ possibly_out_of_time: adc_imm: { if ( status & st_d ) debug_printf( "Decimal mode not supported\n" ); - fint16 carry = c >> 8 & 1; - fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + int16_t carry = c >> 8 & 1; + int16_t ov = (a ^ 0x80) + carry + (int8_t) data; // sign-extend status &= ~st_v; status |= ov >> 2 & 0x40; c = nz = a + data + carry; @@ -771,7 +765,7 @@ possibly_out_of_time: case 0x2A: { // ROL A nz = a << 1; - fint16 temp = c >> 8 & 1; + int16_t temp = c >> 8 & 1; c = nz; nz |= temp; a = (uint8_t) nz; @@ -877,7 +871,7 @@ possibly_out_of_time: case 0xD6: // DEC zp,x data = uint8_t (data + x); case 0xC6: // DEC zp - nz = (unsigned) -1; + nz = (uint16_t) -1; add_nz_zp: nz += READ_LOW( data ); write_nz_zp: @@ -902,7 +896,7 @@ possibly_out_of_time: case 0xCE: // DEC abs data = GET_ADDR(); dec_ptr: - nz = (unsigned) -1; + nz = (uint16_t) -1; inc_common: FLUSH_TIME(); nz += READ( data ); @@ -942,7 +936,7 @@ possibly_out_of_time: goto loop; #define SWAP_REGS( r1, r2 ) {\ - fuint8 t = r1;\ + uint8_t t = r1;\ r1 = r2;\ r2 = t;\ goto loop;\ @@ -984,7 +978,7 @@ possibly_out_of_time: goto loop; case 0x40:{// RTI - fuint8 temp = READ_LOW( sp ); + uint8_t temp = READ_LOW( sp ); pc = READ_LOW( 0x100 | (sp - 0xFF) ); pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; sp = (sp - 0xFD) | 0x100; @@ -1018,8 +1012,8 @@ possibly_out_of_time: goto loop; case 0x28:{// PLP - fuint8 temp = POP(); - fuint8 changed = status ^ temp; + uint8_t temp = POP(); + uint8_t changed = status ^ temp; SET_STATUS( temp ); if ( !(changed & st_i) ) goto loop; // I flag didn't change @@ -1030,7 +1024,7 @@ possibly_out_of_time: #undef POP case 0x08: { // PHP - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); PUSH( temp | st_b ); goto loop; @@ -1039,7 +1033,7 @@ possibly_out_of_time: // Flags case 0x38: // SEC - c = (unsigned) ~0; + c = (uint16_t) ~0; goto loop; case 0x18: // CLC @@ -1107,7 +1101,7 @@ possibly_out_of_time: // Special case 0x53:{// TAM - fuint8 const bits = data; // avoid using data across function call + uint8_t const bits = data; // avoid using data across function call pc++; for ( int i = 0; i < 8; i++ ) if ( bits & (1 << i) ) @@ -1131,7 +1125,7 @@ possibly_out_of_time: case 0x03: // ST0 case 0x13: // ST1 case 0x23:{// ST2 - fuint16 addr = opcode >> 4; + uint16_t addr = opcode >> 4; if ( addr ) addr++; pc++; @@ -1153,7 +1147,7 @@ possibly_out_of_time: goto loop; case 0xF4: { // SET - //fuint16 operand = GET_MSB(); + //uint16_t operand = GET_MSB(); debug_printf( "SET not handled\n" ); //switch ( data ) //{ @@ -1165,10 +1159,10 @@ possibly_out_of_time: // Block transfer { - fuint16 in_alt; - fint16 in_inc; - fuint16 out_alt; - fint16 out_inc; + uint16_t in_alt; + int16_t in_inc; + uint16_t out_alt; + int16_t out_inc; case 0xE3: // TIA in_alt = 0; @@ -1199,8 +1193,8 @@ possibly_out_of_time: in_alt = 0; out_alt = 0; bxfer: - fuint16 in = GET_LE16( instr + 0 ); - fuint16 out = GET_LE16( instr + 2 ); + uint16_t in = GET_LE16( instr + 0 ); + uint16_t out = GET_LE16( instr + 2 ); int count = GET_LE16( instr + 4 ); if ( !count ) count = 0x10000; @@ -1212,7 +1206,7 @@ possibly_out_of_time: do { // TODO: reads from $0800-$1400 in I/O page return 0 and don't access I/O - fuint8 t = READ( in ); + uint8_t t = READ( in ); in += in_inc; in &= 0xFFFF; s.time += 6; @@ -1232,7 +1226,6 @@ possibly_out_of_time: // Illegal default: - assert( (unsigned) opcode <= 0xFF ); debug_printf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 ); illegal_encountered = true; goto loop; @@ -1253,7 +1246,7 @@ interrupt: pc = GET_LE16( &READ_PROG( 0xFFF0 ) + result_ ); sp = (sp - 3) | 0x100; - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); if ( result_ == 6 ) temp |= st_b; @@ -1290,7 +1283,7 @@ out_of_time: r.y = y; { - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); r.status = temp; } diff --git a/game-music-emu/gme/Hes_Cpu.h b/game-music-emu/gme/Hes_Cpu.h index d46a0b8a8..cec46fa9e 100644 --- a/game-music-emu/gme/Hes_Cpu.h +++ b/game-music-emu/gme/Hes_Cpu.h @@ -1,6 +1,6 @@ // PC Engine CPU emulator for use with HES music files -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef HES_CPU_H #define HES_CPU_H @@ -12,8 +12,6 @@ enum { future_hes_time = INT_MAX / 2 + 1 }; class Hes_Cpu { public: - typedef BOOST::uint8_t uint8_t; - void reset(); enum { page_size = 0x2000 }; @@ -27,7 +25,7 @@ public: // not kept updated during a call to run() struct registers_t { - BOOST::uint16_t pc; + uint16_t pc; uint8_t a; uint8_t x; uint8_t y; @@ -86,7 +84,7 @@ private: inline int update_end_time( hes_time_t end, hes_time_t irq ); }; -inline BOOST::uint8_t const* Hes_Cpu::get_code( hes_addr_t addr ) +inline uint8_t const* Hes_Cpu::get_code( hes_addr_t addr ) { return state->code_map [addr >> page_shift] + addr #if !BLARGG_NONPORTABLE diff --git a/game-music-emu/gme/Hes_Emu.cpp b/game-music-emu/gme/Hes_Emu.cpp index e3c700dbc..818691fdc 100644 --- a/game-music-emu/gme/Hes_Emu.cpp +++ b/game-music-emu/gme/Hes_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Hes_Emu.h" @@ -133,7 +133,7 @@ static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; } static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; } static gme_type_t_ const gme_hes_type_ = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 }; -gme_type_t const gme_hes_type = &gme_hes_type_; +BLARGG_EXPORT extern gme_type_t const gme_hes_type = &gme_hes_type_; // Setup diff --git a/game-music-emu/gme/Hes_Emu.h b/game-music-emu/gme/Hes_Emu.h index b4e20fd67..08c1370d4 100644 --- a/game-music-emu/gme/Hes_Emu.h +++ b/game-music-emu/gme/Hes_Emu.h @@ -1,6 +1,6 @@ // TurboGrafx-16/PC Engine HES music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef HES_EMU_H #define HES_EMU_H diff --git a/game-music-emu/gme/Kss_Cpu.cpp b/game-music-emu/gme/Kss_Cpu.cpp index 74230bf71..10eec4fcc 100644 --- a/game-music-emu/gme/Kss_Cpu.cpp +++ b/game-music-emu/gme/Kss_Cpu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ /* Last validated with zexall 2006.11.14 2:19 PM @@ -162,11 +162,6 @@ static byte const ed_dd_timing [0x100] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, }; -// even on x86, using short and unsigned char was slower -typedef int fint16; -typedef unsigned fuint16; -typedef unsigned fuint8; - bool Kss_Cpu::run( cpu_time_t end_time ) { set_end_time( end_time ); @@ -174,8 +169,6 @@ bool Kss_Cpu::run( cpu_time_t end_time ) this->state = &s; bool warning = false; - typedef BOOST::int8_t int8_t; - union { regs_t rg; pairs_t rp; @@ -185,10 +178,10 @@ bool Kss_Cpu::run( cpu_time_t end_time ) rg = this->r.b; cpu_time_t s_time = s.time; - fuint16 pc = r.pc; - fuint16 sp = r.sp; - fuint16 ix = r.ix; // TODO: keep in memory for direct access? - fuint16 iy = r.iy; + uint16_t pc = r.pc; + uint16_t sp = r.sp; + uint16_t ix = r.ix; // TODO: keep in memory for direct access? + uint16_t iy = r.iy; int flags = r.b.flags; goto loop; @@ -210,7 +203,7 @@ loop: uint8_t const* instr = s.read [pc >> page_shift]; #define GET_ADDR() GET_LE16( instr ) - fuint8 opcode; + uint8_t opcode; // TODO: eliminate this special case #if BLARGG_NONPORTABLE @@ -243,7 +236,7 @@ loop: 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F }; - fuint16 data; + uint16_t data; data = base_timing [opcode]; if ( (s_time += data) >= 0 ) goto possibly_out_of_time; @@ -299,7 +292,7 @@ possibly_out_of_time: goto loop; case 0x3A:{// LD A,(addr) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; rg.a = READ( addr ); goto loop; @@ -315,7 +308,7 @@ possibly_out_of_time: // JR // TODO: more efficient way to handle negative branch that wraps PC around #define JR( cond ) {\ - int offset = (BOOST::int8_t) data;\ + int offset = (int8_t) data;\ pc++;\ if ( !(cond) )\ goto jr_not_taken;\ @@ -387,7 +380,7 @@ possibly_out_of_time: case 0xCD:{// CALL addr call_taken: - fuint16 addr = pc + 2; + uint16_t addr = pc + 2; pc = GET_ADDR(); sp = uint16_t (sp - 2); WRITE_WORD( sp, addr ); @@ -395,7 +388,7 @@ possibly_out_of_time: } case 0xFF: // RST - if ( pc > idle_addr ) + if ( pc >= idle_addr ) goto hit_idle_addr; CASE7( C7, CF, D7, DF, E7, EF, F7 ): data = pc; @@ -503,7 +496,7 @@ possibly_out_of_time: add_hl_data: { blargg_ulong sum = rp.hl + data; data ^= rp.hl; - rp.hl = (uint16_t)sum; + rp.hl = sum; flags = (flags & (S80 | Z40 | V04)) | (sum >> 16) | (sum >> 8 & (F20 | F08)) | @@ -693,21 +686,21 @@ possibly_out_of_time: goto loop; case 0x2A:{// LD HL,(addr) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; rp.hl = READ_WORD( addr ); goto loop; } case 0x32:{// LD (addr),A - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE( addr, rg.a ); goto loop; } case 0x22:{// LD (addr),HL - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE_WORD( addr, rp.hl ); goto loop; @@ -730,7 +723,7 @@ possibly_out_of_time: // Rotate case 0x07:{// RLCA - fuint16 temp = rg.a; + uint16_t temp = rg.a; temp = (temp << 1) | (temp >> 7); flags = (flags & (S80 | Z40 | P04)) | (temp & (F20 | F08 | C01)); @@ -739,7 +732,7 @@ possibly_out_of_time: } case 0x0F:{// RRCA - fuint16 temp = rg.a; + uint16_t temp = rg.a; flags = (flags & (S80 | Z40 | P04)) | (temp & C01); temp = (temp << 7) | (temp >> 1); @@ -753,12 +746,12 @@ possibly_out_of_time: flags = (flags & (S80 | Z40 | P04)) | (temp & (F20 | F08)) | (temp >> 8); - rg.a = (uint8_t)temp; + rg.a = temp; goto loop; } case 0x1F:{// RRA - fuint16 temp = (flags << 7) | (rg.a >> 1); + uint16_t temp = (flags << 7) | (rg.a >> 1); flags = (flags & (S80 | Z40 | P04)) | (temp & (F20 | F08)) | (rg.a & C01); @@ -768,7 +761,7 @@ possibly_out_of_time: // Misc case 0x2F:{// CPL - fuint16 temp = ~rg.a; + uint16_t temp = ~rg.a; flags = (flags & (S80 | Z40 | P04 | C01)) | (temp & (F20 | F08)) | (H10 | N02); @@ -794,21 +787,21 @@ possibly_out_of_time: goto loop; case 0xE3:{// EX (SP),HL - fuint16 temp = READ_WORD( sp ); + uint16_t temp = READ_WORD( sp ); WRITE_WORD( sp, rp.hl ); rp.hl = temp; goto loop; } case 0xEB:{// EX DE,HL - fuint16 temp = rp.hl; + uint16_t temp = rp.hl; rp.hl = rp.de; rp.de = temp; goto loop; } case 0xD9:{// EXX DE,HL - fuint16 temp = r.alt.w.bc; + uint16_t temp = r.alt.w.bc; r.alt.w.bc = rp.bc; rp.bc = temp; @@ -849,7 +842,7 @@ possibly_out_of_time: // Rotate left #define RLC( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ result = uint8_t (result << 1) | (result >> 7);\ flags = SZ28P( result ) | (result & C01);\ write;\ @@ -868,7 +861,7 @@ possibly_out_of_time: } #define RL( read, write ) {\ - fuint16 result = (read << 1) | (flags & C01);\ + uint16_t result = (read << 1) | (flags & C01);\ flags = SZ28PC( result );\ write;\ goto loop;\ @@ -886,7 +879,7 @@ possibly_out_of_time: } #define SLA( read, add, write ) {\ - fuint16 result = (read << 1) | add;\ + uint16_t result = (read << 1) | add;\ flags = SZ28PC( result );\ write;\ goto loop;\ @@ -917,7 +910,7 @@ possibly_out_of_time: // Rotate right #define RRC( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ flags = result & C01;\ result = uint8_t (result << 7) | (result >> 1);\ flags |= SZ28P( result );\ @@ -937,8 +930,8 @@ possibly_out_of_time: } #define RR( read, write ) {\ - fuint8 result = read;\ - fuint8 temp = result & C01;\ + uint8_t result = read;\ + uint8_t temp = result & C01;\ result = uint8_t (flags << 7) | (result >> 1);\ flags = SZ28P( result ) | temp;\ write;\ @@ -957,7 +950,7 @@ possibly_out_of_time: } #define SRA( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ flags = result & C01;\ result = (result & 0x80) | (result >> 1);\ flags |= SZ28P( result );\ @@ -977,7 +970,7 @@ possibly_out_of_time: } #define SRL( read, write ) {\ - fuint8 result = read;\ + uint8_t result = read;\ flags = result & C01;\ result >>= 1;\ flags |= SZ28P( result );\ @@ -1085,7 +1078,7 @@ possibly_out_of_time: blargg_ulong sum = temp + (flags & C01); flags = ~data >> 2 & N02; if ( flags ) - sum = (blargg_ulong)-(blargg_long)sum; + sum = -sum; sum += rp.hl; temp ^= rp.hl; temp ^= sum; @@ -1093,7 +1086,7 @@ possibly_out_of_time: (temp >> 8 & H10) | (sum >> 8 & (S80 | F20 | F08)) | ((temp - -0x8000) >> 14 & V04); - rp.hl = (uint16_t)sum; + rp.hl = sum; if ( (uint16_t) sum ) goto loop; flags |= Z40; @@ -1121,7 +1114,7 @@ possibly_out_of_time: case 0x43: // LD (ADDR),BC case 0x53: // LD (ADDR),DE temp = R16( data, 4, 0x43 ); - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE_WORD( addr, temp ); goto loop; @@ -1129,21 +1122,21 @@ possibly_out_of_time: case 0x4B: // LD BC,(ADDR) case 0x5B:{// LD DE,(ADDR) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; R16( data, 4, 0x4B ) = READ_WORD( addr ); goto loop; } case 0x7B:{// LD SP,(ADDR) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; sp = READ_WORD( addr ); goto loop; } case 0x67:{// RRD - fuint8 temp = READ( rp.hl ); + uint8_t temp = READ( rp.hl ); WRITE( rp.hl, (rg.a << 4) | (temp >> 4) ); temp = (rg.a & 0xF0) | (temp & 0x0F); flags = (flags & C01) | SZ28P( temp ); @@ -1152,7 +1145,7 @@ possibly_out_of_time: } case 0x6F:{// RLD - fuint8 temp = READ( rp.hl ); + uint8_t temp = READ( rp.hl ); WRITE( rp.hl, (temp << 4) | (rg.a & 0x0F) ); temp = (rg.a & 0xF0) | (temp >> 4); flags = (flags & C01) | SZ28P( temp ); @@ -1176,7 +1169,7 @@ possibly_out_of_time: case 0xA1: // CPI case 0xB1: // CPIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = READ( addr ); @@ -1209,7 +1202,7 @@ possibly_out_of_time: case 0xA0: // LDI case 0xB0: // LDIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = READ( addr ); @@ -1241,7 +1234,7 @@ possibly_out_of_time: case 0xA3: // OUTI case 0xB3: // OTIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = READ( addr ); @@ -1267,7 +1260,7 @@ possibly_out_of_time: case 0xB2: // INIR inc = +1; - fuint16 addr = rp.hl; + uint16_t addr = rp.hl; rp.hl = addr + inc; int temp = IN( rp.bc ); @@ -1332,7 +1325,7 @@ possibly_out_of_time: //////////////////////////////////////// DD/FD prefix { - fuint16 ixy; + uint16_t ixy; case 0xDD: ixy = ix; goto ix_prefix; @@ -1528,7 +1521,7 @@ possibly_out_of_time: goto loop; case 0x22:{// LD (ADDR),IXY - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); pc += 2; WRITE_WORD( addr, ixy ); goto loop; @@ -1540,7 +1533,7 @@ possibly_out_of_time: goto set_ixy; case 0x2A:{// LD IXY,(addr) - fuint16 addr = GET_ADDR(); + uint16_t addr = GET_ADDR(); ixy = READ_WORD( addr ); pc += 2; goto set_ixy; @@ -1564,7 +1557,7 @@ possibly_out_of_time: case 0x3E: goto srl_data_addr; // SRL (IXY) CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) - fuint8 temp = READ( data ); + uint8_t temp = READ( data ); int masked = temp & 1 << (data2 >> 3 & 7); flags = (flags & C01) | H10 | (masked & S80) | @@ -1666,7 +1659,7 @@ possibly_out_of_time: goto loop; case 0xE3:{// EX (SP),IXY - fuint16 temp = READ_WORD( sp ); + uint16_t temp = READ_WORD( sp ); WRITE_WORD( sp, ixy ); ixy = temp; goto set_ixy; diff --git a/game-music-emu/gme/Kss_Cpu.h b/game-music-emu/gme/Kss_Cpu.h index aec24e9d5..d31864cd3 100644 --- a/game-music-emu/gme/Kss_Cpu.h +++ b/game-music-emu/gme/Kss_Cpu.h @@ -1,6 +1,6 @@ // Z80 CPU emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef KSS_CPU_H #define KSS_CPU_H @@ -15,8 +15,6 @@ void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data ); class Kss_Cpu { public: - typedef BOOST::uint8_t uint8_t; - // Clear registers and map all pages to unmapped void reset( void* unmapped_write, void const* unmapped_read ); @@ -39,8 +37,6 @@ public: void set_time( cpu_time_t t ) { state->time = t - state->base; } void adjust_time( int delta ) { state->time += delta; } - typedef BOOST::uint16_t uint16_t; - #if BLARGG_BIG_ENDIAN struct regs_t { uint8_t b, c, d, e, h, l, flags, a; }; #else @@ -104,12 +100,12 @@ public: #define KSS_CPU_PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) #endif -inline BOOST::uint8_t* Kss_Cpu::write( unsigned addr ) +inline uint8_t* Kss_Cpu::write( unsigned addr ) { return state->write [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr ); } -inline BOOST::uint8_t const* Kss_Cpu::read( unsigned addr ) +inline uint8_t const* Kss_Cpu::read( unsigned addr ) { return state->read [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr ); } diff --git a/game-music-emu/gme/Kss_Emu.cpp b/game-music-emu/gme/Kss_Emu.cpp index def6ee199..fd4905ce3 100644 --- a/game-music-emu/gme/Kss_Emu.cpp +++ b/game-music-emu/gme/Kss_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Kss_Emu.h" @@ -102,7 +102,7 @@ static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; } static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; } static gme_type_t_ const gme_kss_type_ = { "MSX", 256, &new_kss_emu, &new_kss_file, "KSS", 0x03 }; -gme_type_t const gme_kss_type = &gme_kss_type_; +BLARGG_EXPORT extern gme_type_t const gme_kss_type = &gme_kss_type_; // Setup diff --git a/game-music-emu/gme/Kss_Emu.h b/game-music-emu/gme/Kss_Emu.h index e457f7311..467b28abd 100644 --- a/game-music-emu/gme/Kss_Emu.h +++ b/game-music-emu/gme/Kss_Emu.h @@ -1,6 +1,6 @@ // MSX computer KSS music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef KSS_EMU_H #define KSS_EMU_H diff --git a/game-music-emu/gme/Kss_Scc_Apu.cpp b/game-music-emu/gme/Kss_Scc_Apu.cpp index 1ab14bd00..bb84b3250 100644 --- a/game-music-emu/gme/Kss_Scc_Apu.cpp +++ b/game-music-emu/gme/Kss_Scc_Apu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Kss_Scc_Apu.h" @@ -43,7 +43,7 @@ void Scc_Apu::run_until( blip_time_t end_time ) volume = (regs [0x8A + index] & 0x0F) * (amp_range / 256 / 15); } - BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size; + int8_t const* wave = (int8_t*) regs + index * wave_size; if ( index == osc_count - 1 ) wave -= wave_size; // last two oscs share wave { diff --git a/game-music-emu/gme/Kss_Scc_Apu.h b/game-music-emu/gme/Kss_Scc_Apu.h index c8c69f118..eda5747fe 100644 --- a/game-music-emu/gme/Kss_Scc_Apu.h +++ b/game-music-emu/gme/Kss_Scc_Apu.h @@ -1,6 +1,6 @@ // Konami SCC sound chip emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef KSS_SCC_APU_H #define KSS_SCC_APU_H diff --git a/game-music-emu/gme/M3u_Playlist.cpp b/game-music-emu/gme/M3u_Playlist.cpp index ffd7d82c3..e751d4cc8 100644 --- a/game-music-emu/gme/M3u_Playlist.cpp +++ b/game-music-emu/gme/M3u_Playlist.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "M3u_Playlist.h" #include "Music_Emu.h" @@ -407,7 +407,7 @@ blargg_err_t M3u_Playlist::parse() blargg_err_t M3u_Playlist::load( Data_Reader& in ) { RETURN_ERR( data.resize( in.remain() + 1 ) ); - RETURN_ERR( in.read( data.begin(), long(data.size() - 1) ) ); + RETURN_ERR( in.read( data.begin(), data.size() - 1 ) ); return parse(); } diff --git a/game-music-emu/gme/M3u_Playlist.h b/game-music-emu/gme/M3u_Playlist.h index fe90dd9f8..6757b7cfb 100644 --- a/game-music-emu/gme/M3u_Playlist.h +++ b/game-music-emu/gme/M3u_Playlist.h @@ -1,6 +1,6 @@ // M3U playlist file parser, with support for subtrack information -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef M3U_PLAYLIST_H #define M3U_PLAYLIST_H @@ -43,7 +43,7 @@ public: int repeat; // count }; entry_t const& operator [] ( int i ) const { return entries [i]; } - int size() const { return int(entries.size()); } + int size() const { return entries.size(); } void clear(); diff --git a/game-music-emu/gme/Multi_Buffer.cpp b/game-music-emu/gme/Multi_Buffer.cpp index b86142f63..5f000ceeb 100644 --- a/game-music-emu/gme/Multi_Buffer.cpp +++ b/game-music-emu/gme/Multi_Buffer.cpp @@ -81,7 +81,7 @@ void Stereo_Buffer::clock_rate( long rate ) void Stereo_Buffer::bass_freq( int bass ) { - for ( unsigned i = 0; i < buf_count; i++ ) + for ( int i = 0; i < buf_count; i++ ) bufs [i].bass_freq( bass ); } @@ -96,7 +96,7 @@ void Stereo_Buffer::clear() void Stereo_Buffer::end_frame( blip_time_t clock_count ) { stereo_added = 0; - for ( unsigned i = 0; i < buf_count; i++ ) + for ( int i = 0; i < buf_count; i++ ) { stereo_added |= bufs [i].clear_modified() << i; bufs [i].end_frame( clock_count ); @@ -161,18 +161,18 @@ void Stereo_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count ) int c = BLIP_READER_READ( center ); blargg_long l = c + BLIP_READER_READ( left ); blargg_long r = c + BLIP_READER_READ( right ); - if ( (BOOST::int16_t) l != l ) + if ( (int16_t) l != l ) l = 0x7FFF - (l >> 24); BLIP_READER_NEXT( center, bass ); - if ( (BOOST::int16_t) r != r ) + if ( (int16_t) r != r ) r = 0x7FFF - (r >> 24); BLIP_READER_NEXT( left, bass ); BLIP_READER_NEXT( right, bass ); - out [0] = (blip_sample_t)l; - out [1] = (blip_sample_t)r; + out [0] = l; + out [1] = r; out += 2; } @@ -191,18 +191,18 @@ void Stereo_Buffer::mix_stereo_no_center( blip_sample_t* out_, blargg_long count for ( ; count; --count ) { blargg_long l = BLIP_READER_READ( left ); - if ( (BOOST::int16_t) l != l ) + if ( (int16_t) l != l ) l = 0x7FFF - (l >> 24); blargg_long r = BLIP_READER_READ( right ); - if ( (BOOST::int16_t) r != r ) + if ( (int16_t) r != r ) r = 0x7FFF - (r >> 24); BLIP_READER_NEXT( left, bass ); BLIP_READER_NEXT( right, bass ); - out [0] = (blip_sample_t)l; - out [1] = (blip_sample_t)r; + out [0] = l; + out [1] = r; out += 2; } @@ -219,12 +219,12 @@ void Stereo_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) for ( ; count; --count ) { blargg_long s = BLIP_READER_READ( center ); - if ( (BOOST::int16_t) s != s ) + if ( (int16_t) s != s ) s = 0x7FFF - (s >> 24); BLIP_READER_NEXT( center, bass ); - out [0] = (blip_sample_t)s; - out [1] = (blip_sample_t)s; + out [0] = s; + out [1] = s; out += 2; } diff --git a/game-music-emu/gme/Music_Emu.cpp b/game-music-emu/gme/Music_Emu.cpp index b7fc4993b..66ffa2d96 100644 --- a/game-music-emu/gme/Music_Emu.cpp +++ b/game-music-emu/gme/Music_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Music_Emu.h" @@ -18,7 +18,6 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" -int const stereo = 2; // number of channels for stereo int const silence_max = 6; // seconds int const silence_threshold = 0x10; long const fade_block_size = 512; @@ -52,7 +51,7 @@ void Music_Emu::unload() Music_Emu::Music_Emu() { effects_buffer = 0; - + multi_channel_ = false; sample_rate_ = 0; mute_mask_ = 0; tempo_ = 1.0; @@ -96,6 +95,25 @@ void Music_Emu::set_equalizer( equalizer_t const& eq ) set_equalizer_( eq ); } +bool Music_Emu::multi_channel() const +{ + return this->multi_channel_; +} + +blargg_err_t Music_Emu::set_multi_channel( bool ) +{ + // by default not supported, derived may override this + return "unsupported for this emulator type"; +} + +blargg_err_t Music_Emu::set_multi_channel_( bool isEnabled ) +{ + // multi channel support must be set at the very beginning + require( !sample_rate() ); + multi_channel_ = isEnabled; + return 0; +} + void Music_Emu::mute_voice( int index, bool mute ) { require( (unsigned) index < (unsigned) voice_count() ); @@ -145,7 +163,7 @@ blargg_err_t Music_Emu::start_track( int track ) if ( !ignore_silence_ ) { // play until non-silence or end of track - for ( long end = max_initial_silence * stereo * sample_rate(); emu_time < end; ) + for ( long end = max_initial_silence * out_channels() * sample_rate(); emu_time < end; ) { fill_buf(); if ( buf_remain | (int) emu_track_ended_ ) @@ -175,7 +193,7 @@ blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const { blargg_long sec = msec / 1000; msec -= sec * 1000; - return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo; + return (sec * sample_rate() + msec * sample_rate() / 1000) * out_channels(); } long Music_Emu::tell_samples() const @@ -185,7 +203,7 @@ long Music_Emu::tell_samples() const long Music_Emu::tell() const { - blargg_long rate = sample_rate() * stereo; + blargg_long rate = sample_rate() * out_channels(); blargg_long sec = out_time / rate; return sec * 1000 + (out_time - sec * rate) * 1000 / rate; } @@ -263,7 +281,7 @@ blargg_err_t Music_Emu::skip_( long count ) void Music_Emu::set_fade( long start_msec, long length_msec ) { - fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / out_channels()); fade_start = msec_to_samples( start_msec ); } @@ -345,7 +363,7 @@ blargg_err_t Music_Emu::play( long out_count, sample_t* out ) else { require( current_track() >= 0 ); - require( out_count % stereo == 0 ); + require( out_count % out_channels() == 0 ); assert( emu_time >= out_time ); @@ -357,7 +375,7 @@ blargg_err_t Music_Emu::play( long out_count, sample_t* out ) { // during a run of silence, run emulator at >=2x speed so it gets ahead long ahead_time = silence_lookahead * (out_time + out_count - silence_time) + silence_time; - while ( emu_time < ahead_time && !(buf_remain || emu_track_ended_) ) + while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) ) fill_buf(); // fill with silence @@ -365,7 +383,7 @@ blargg_err_t Music_Emu::play( long out_count, sample_t* out ) memset( out, 0, pos * sizeof *out ); silence_count -= pos; - if ( emu_time - silence_time > silence_max * stereo * sample_rate() ) + if ( emu_time - silence_time > silence_max * out_channels() * sample_rate() ) { track_ended_ = emu_track_ended_ = true; silence_count = 0; @@ -401,7 +419,7 @@ blargg_err_t Music_Emu::play( long out_count, sample_t* out ) } } - if ( out_time > fade_start ) + if ( fade_start >= 0 && out_time > fade_start ) handle_fade( out_count, out ); } out_time += out_count; diff --git a/game-music-emu/gme/Music_Emu.h b/game-music-emu/gme/Music_Emu.h index d98f7ce7e..ee0f8379c 100644 --- a/game-music-emu/gme/Music_Emu.h +++ b/game-music-emu/gme/Music_Emu.h @@ -1,6 +1,6 @@ // Common interface to game music file emulators -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef MUSIC_EMU_H #define MUSIC_EMU_H @@ -13,6 +13,11 @@ public: // Set output sample rate. Must be called only once before loading file. blargg_err_t set_sample_rate( long sample_rate ); + + // specifies if all 8 voices get rendered to their own stereo channel + // default implementation of Music_Emu always returns not supported error (i.e. no multichannel support by default) + // derived emus must override this if they support multichannel rendering + virtual blargg_err_t set_multi_channel( bool is_enabled ); // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t start_track( int ); @@ -35,6 +40,8 @@ public: // Names of voices const char** voice_names() const; + + bool multi_channel() const; // Track status/control @@ -127,6 +134,7 @@ protected: double gain() const { return gain_; } double tempo() const { return tempo_; } void remute_voices(); + blargg_err_t set_multi_channel_( bool is_enabled ); virtual blargg_err_t set_sample_rate_( long sample_rate ) = 0; virtual void set_equalizer_( equalizer_t const& ) { } @@ -149,7 +157,11 @@ private: int mute_mask_; double tempo_; double gain_; - + bool multi_channel_; + + // returns the number of output channels, i.e. usually 2 for stereo, unlesss multi_channel_ == true + int out_channels() const { return this->multi_channel() ? 2*8 : 2; } + long sample_rate_; blargg_long msec_to_samples( blargg_long msec ) const; @@ -179,7 +191,7 @@ private: void emu_play( long count, sample_t* out ); Multi_Buffer* effects_buffer; - friend Music_Emu* gme_new_emu( gme_type_t, int ); + friend Music_Emu* gme_internal_new_emu_( gme_type_t, int, bool ); friend void gme_set_stereo_depth( Music_Emu*, double ); }; diff --git a/game-music-emu/gme/Nes_Cpu.cpp b/game-music-emu/gme/Nes_Cpu.cpp index ac548d0ff..5eb0862a3 100644 --- a/game-music-emu/gme/Nes_Cpu.cpp +++ b/game-music-emu/gme/Nes_Cpu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Nes_Cpu.h" @@ -114,25 +114,20 @@ void Nes_Cpu::map_code( nes_addr_t start, unsigned size, void const* data, bool #define GET_SP() ((sp - 1) & 0xFF) #define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) -// even on x86, using short and unsigned char was slower -typedef int fint16; -typedef unsigned fuint16; -typedef unsigned fuint8; - bool Nes_Cpu::run( nes_time_t end_time ) { set_end_time( end_time ); state_t s = this->state_; this->state = &s; // even on x86, using s.time in place of s_time was slower - fint16 s_time = s.time; + int16_t s_time = s.time; // registers - fuint16 pc = r.pc; - fuint8 a = r.a; - fuint8 x = r.x; - fuint8 y = r.y; - fuint16 sp; + uint16_t pc = r.pc; + uint8_t a = r.a; + uint8_t x = r.x; + uint8_t y = r.y; + uint16_t sp; SET_SP( r.sp ); // status flags @@ -152,11 +147,11 @@ bool Nes_Cpu::run( nes_time_t end_time ) nz |= ~in & st_z;\ } while ( 0 ) - fuint8 status; - fuint16 c; // carry set if (c & 0x100) != 0 - fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + uint8_t status; + uint16_t c; // carry set if (c & 0x100) != 0 + uint16_t nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 { - fuint8 temp = r.status; + uint8_t temp = r.status; SET_STATUS( temp ); } @@ -173,7 +168,7 @@ loop: check( -32768 <= s_time && s_time < 32767 ); uint8_t const* instr = s.code_map [pc >> page_bits]; - fuint8 opcode; + uint8_t opcode; // TODO: eliminate this special case #if BLARGG_NONPORTABLE @@ -206,7 +201,7 @@ loop: 3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 // F }; // 0x00 was 7 and 0xF2 was 2 - fuint16 data; + uint16_t data; #if !BLARGG_CPU_X86 if ( s_time >= 0 ) @@ -247,13 +242,13 @@ possibly_out_of_time: #define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; #define IND_Y( cross, out ) {\ - fuint16 temp = READ_LOW( data ) + y;\ + uint16_t temp = READ_LOW( data ) + y;\ out = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\ cross( temp );\ } #define IND_X( out ) {\ - fuint16 temp = data + x;\ + uint16_t temp = data + x;\ out = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) );\ } @@ -288,10 +283,10 @@ imm##op: // TODO: more efficient way to handle negative branch that wraps PC around #define BRANCH( cond )\ {\ - fint16 offset = (BOOST::int8_t) data;\ - fuint16 extra_clock = (++pc & 0xFF) + offset;\ + int16_t offset = (int8_t) data;\ + uint16_t extra_clock = (++pc & 0xFF) + offset;\ if ( !(cond) ) goto dec_clock_loop;\ - pc = BOOST::uint16_t (pc + offset);\ + pc = uint16_t (pc + offset);\ s_time += extra_clock >> 8 & 1;\ goto loop;\ } @@ -312,7 +307,7 @@ imm##op: BRANCH( (uint8_t) nz ); case 0x20: { // JSR - fuint16 temp = pc + 1; + uint16_t temp = pc + 1; pc = GET_ADDR(); WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); sp = (sp - 2) | 0x100; @@ -378,7 +373,7 @@ imm##op: goto loop; { - fuint16 addr; + uint16_t addr; case 0x99: // STA abs,Y addr = y + GET_ADDR(); @@ -434,7 +429,7 @@ imm##op: // common read instructions { - fuint16 addr; + uint16_t addr; case 0xA1: // LDA (ind,X) IND_X( addr ) @@ -550,7 +545,7 @@ imm##op: } { - fuint8 temp; + uint8_t temp; case 0x8C: // STY abs temp = y; goto store_abs; @@ -659,8 +654,8 @@ imm##op: ARITH_ADDR_MODES( 0x65 ) // ADC adc_imm: { - fint16 carry = c >> 8 & 1; - fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + int16_t carry = c >> 8 & 1; + int16_t ov = (a ^ 0x80) + carry + (int8_t) data; // sign-extend status &= ~st_v; status |= ov >> 2 & 0x40; c = nz = a + data + carry; @@ -688,7 +683,7 @@ imm##op: case 0x2A: { // ROL A nz = a << 1; - fint16 temp = c >> 8 & 1; + int16_t temp = c >> 8 & 1; c = nz; nz |= temp; a = (uint8_t) nz; @@ -780,7 +775,7 @@ imm##op: case 0xD6: // DEC zp,x data = uint8_t (data + x); case 0xC6: // DEC zp - nz = (unsigned) -1; + nz = (uint16_t) -1; add_nz_zp: nz += READ_LOW( data ); write_nz_zp: @@ -805,7 +800,7 @@ imm##op: case 0xCE: // DEC abs data = GET_ADDR(); dec_ptr: - nz = (unsigned) -1; + nz = (uint16_t) -1; inc_common: FLUSH_TIME(); nz += READ( data ); @@ -846,7 +841,7 @@ imm##op: goto loop; case 0x40:{// RTI - fuint8 temp = READ_LOW( sp ); + uint8_t temp = READ_LOW( sp ); pc = READ_LOW( 0x100 | (sp - 0xFF) ); pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; sp = (sp - 0xFD) | 0x100; @@ -863,9 +858,9 @@ imm##op: } case 0x28:{// PLP - fuint8 temp = READ_LOW( sp ); + uint8_t temp = READ_LOW( sp ); sp = (sp - 0xFF) | 0x100; - fuint8 changed = status ^ temp; + uint8_t changed = status ^ temp; SET_STATUS( temp ); if ( !(changed & st_i) ) goto loop; // I flag didn't change @@ -875,7 +870,7 @@ imm##op: } case 0x08: { // PHP - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); PUSH( temp | (st_b | st_r) ); goto loop; @@ -897,7 +892,7 @@ imm##op: // Flags case 0x38: // SEC - c = (unsigned) ~0; + c = (uint16_t) ~0; goto loop; case 0x18: // CLC @@ -983,12 +978,6 @@ imm##op: case bad_opcode: // HLT pc--; - if ( pc > 0xFFFF ) - { - // handle wrap-around (assumes caller has put page of HLT at 0x10000) - pc &= 0xFFFF; - goto loop; - } case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: goto stop; @@ -1003,8 +992,8 @@ imm##op: static unsigned char const illop_lens [8] = { 0x40, 0x40, 0x40, 0x80, 0x40, 0x40, 0x80, 0xA0 }; - fuint8 opcode = instr [-1]; - fint16 len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + uint8_t opcode = instr [-1]; + int16_t len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; if ( opcode == 0x9C ) len = 2; pc += len; @@ -1035,7 +1024,7 @@ interrupt: pc = GET_LE16( &READ_PROG( 0xFFFA ) + result_ ); sp = (sp - 3) | 0x100; - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); temp |= st_r; if ( result_ ) @@ -1071,7 +1060,7 @@ stop: r.y = y; { - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); r.status = temp; } diff --git a/game-music-emu/gme/Nes_Cpu.h b/game-music-emu/gme/Nes_Cpu.h index c139e069f..878b5ba5c 100644 --- a/game-music-emu/gme/Nes_Cpu.h +++ b/game-music-emu/gme/Nes_Cpu.h @@ -1,6 +1,6 @@ // NES 6502 CPU emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef NES_CPU_H #define NES_CPU_H @@ -12,8 +12,6 @@ enum { future_nes_time = INT_MAX / 2 + 1 }; class Nes_Cpu { public: - typedef BOOST::uint8_t uint8_t; - // Clear registers, map low memory and its three mirrors to address 0, // and mirror unmapped_page in remaining memory void reset( void const* unmapped_page = 0 ); @@ -32,12 +30,12 @@ public: // NES 6502 registers. Not kept updated during a call to run(). struct registers_t { - BOOST::uint16_t pc; - BOOST::uint8_t a; - BOOST::uint8_t x; - BOOST::uint8_t y; - BOOST::uint8_t status; - BOOST::uint8_t sp; + uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; }; registers_t r; @@ -84,7 +82,7 @@ private: inline int update_end_time( nes_time_t end, nes_time_t irq ); }; -inline BOOST::uint8_t const* Nes_Cpu::get_code( nes_addr_t addr ) +inline uint8_t const* Nes_Cpu::get_code( nes_addr_t addr ) { return state->code_map [addr >> page_bits] + addr #if !BLARGG_NONPORTABLE diff --git a/game-music-emu/gme/Nes_Fme7_Apu.cpp b/game-music-emu/gme/Nes_Fme7_Apu.cpp index bc8ca7f48..93973e409 100644 --- a/game-music-emu/gme/Nes_Fme7_Apu.cpp +++ b/game-music-emu/gme/Nes_Fme7_Apu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Nes_Fme7_Apu.h" diff --git a/game-music-emu/gme/Nes_Fme7_Apu.h b/game-music-emu/gme/Nes_Fme7_Apu.h index 9069bd06a..b79ed6f5e 100644 --- a/game-music-emu/gme/Nes_Fme7_Apu.h +++ b/game-music-emu/gme/Nes_Fme7_Apu.h @@ -1,6 +1,6 @@ // Sunsoft FME-7 sound emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef NES_FME7_APU_H #define NES_FME7_APU_H @@ -10,10 +10,10 @@ struct fme7_apu_state_t { enum { reg_count = 14 }; - BOOST::uint8_t regs [reg_count]; - BOOST::uint8_t phases [3]; // 0 or 1 - BOOST::uint8_t latch; - BOOST::uint16_t delays [3]; // a, b, c + uint8_t regs [reg_count]; + uint8_t phases [3]; // 0 or 1 + uint8_t latch; + uint16_t delays [3]; // a, b, c }; class Nes_Fme7_Apu : private fme7_apu_state_t { diff --git a/game-music-emu/gme/Nes_Namco_Apu.cpp b/game-music-emu/gme/Nes_Namco_Apu.cpp index f3235b383..3e5fc1491 100644 --- a/game-music-emu/gme/Nes_Namco_Apu.cpp +++ b/game-music-emu/gme/Nes_Namco_Apu.cpp @@ -90,7 +90,7 @@ void Nes_Namco_Apu::run_until( blip_time_t nes_end_time ) osc.delay = 0; if ( time < end_time ) { - const BOOST::uint8_t* osc_reg = ® [i * 8 + 0x40]; + const uint8_t* osc_reg = ® [i * 8 + 0x40]; if ( !(osc_reg [4] & 0xE0) ) continue; diff --git a/game-music-emu/gme/Nes_Namco_Apu.h b/game-music-emu/gme/Nes_Namco_Apu.h index db5fea4bf..876d85e0a 100644 --- a/game-music-emu/gme/Nes_Namco_Apu.h +++ b/game-music-emu/gme/Nes_Namco_Apu.h @@ -54,24 +54,24 @@ private: int addr_reg; enum { reg_count = 0x80 }; - BOOST::uint8_t reg [reg_count]; + uint8_t reg [reg_count]; Blip_Synth synth; - BOOST::uint8_t& access(); + uint8_t& access(); void run_until( blip_time_t ); }; /* struct namco_state_t { - BOOST::uint8_t regs [0x80]; - BOOST::uint8_t addr; - BOOST::uint8_t unused; - BOOST::uint8_t positions [8]; - BOOST::uint32_t delays [8]; + uint8_t regs [0x80]; + uint8_t addr; + uint8_t unused; + uint8_t positions [8]; + uint32_t delays [8]; }; */ -inline BOOST::uint8_t& Nes_Namco_Apu::access() +inline uint8_t& Nes_Namco_Apu::access() { int addr = addr_reg & 0x7F; if ( addr_reg & 0x80 ) diff --git a/game-music-emu/gme/Nes_Vrc6_Apu.h b/game-music-emu/gme/Nes_Vrc6_Apu.h index 18722233f..23a6519fc 100644 --- a/game-music-emu/gme/Nes_Vrc6_Apu.h +++ b/game-music-emu/gme/Nes_Vrc6_Apu.h @@ -40,7 +40,7 @@ private: struct Vrc6_Osc { - BOOST::uint8_t regs [3]; + uint8_t regs [3]; Blip_Buffer* output; int delay; int last_amp; @@ -66,11 +66,11 @@ private: struct vrc6_apu_state_t { - BOOST::uint8_t regs [3] [3]; - BOOST::uint8_t saw_amp; - BOOST::uint16_t delays [3]; - BOOST::uint8_t phases [3]; - BOOST::uint8_t unused; + uint8_t regs [3] [3]; + uint8_t saw_amp; + uint16_t delays [3]; + uint8_t phases [3]; + uint8_t unused; }; inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) diff --git a/game-music-emu/gme/Nsf_Emu.cpp b/game-music-emu/gme/Nsf_Emu.cpp index eab4bfbfd..74d76850e 100644 --- a/game-music-emu/gme/Nsf_Emu.cpp +++ b/game-music-emu/gme/Nsf_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Nsf_Emu.h" @@ -130,7 +130,7 @@ static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; } static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; } static gme_type_t_ const gme_nsf_type_ = { "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 }; -gme_type_t const gme_nsf_type = &gme_nsf_type_; +BLARGG_EXPORT extern gme_type_t const gme_nsf_type = &gme_nsf_type_; // Setup diff --git a/game-music-emu/gme/Nsf_Emu.h b/game-music-emu/gme/Nsf_Emu.h index 0b001686c..e538b1b30 100644 --- a/game-music-emu/gme/Nsf_Emu.h +++ b/game-music-emu/gme/Nsf_Emu.h @@ -1,6 +1,6 @@ // Nintendo NES/Famicom NSF music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef NSF_EMU_H #define NSF_EMU_H diff --git a/game-music-emu/gme/Nsfe_Emu.cpp b/game-music-emu/gme/Nsfe_Emu.cpp index 5559bca9b..035f99dee 100644 --- a/game-music-emu/gme/Nsfe_Emu.cpp +++ b/game-music-emu/gme/Nsfe_Emu.cpp @@ -1,6 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ - -#define _CRT_SECURE_NO_WARNINGS +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Nsfe_Emu.h" @@ -37,7 +35,7 @@ inline void Nsfe_Info::unload() void Nsfe_Info::disable_playlist( bool b ) { playlist_disabled = b; - info.track_count = (byte)playlist.size(); + info.track_count = playlist.size(); if ( !info.track_count || playlist_disabled ) info.track_count = actual_track_count_; } @@ -136,6 +134,9 @@ blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu ) RETURN_ERR( in.read( block_header, sizeof block_header ) ); blargg_long size = get_le32( block_header [0] ); blargg_long tag = get_le32( block_header [1] ); + + if ( size < 0 ) + return "Corrupt file"; //debug_printf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) ); @@ -173,7 +174,7 @@ blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu ) blargg_vector chars; blargg_vector strs; RETURN_ERR( read_strs( in, size, chars, strs ) ); - int n = (int)strs.size(); + int n = strs.size(); if ( n > 3 ) copy_str( strs [3], info.dumper, sizeof info.dumper ); @@ -192,7 +193,7 @@ blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu ) case BLARGG_4CHAR('e','m','i','t'): RETURN_ERR( track_times.resize( size / 4 ) ); - RETURN_ERR( in.read( track_times.begin(), (long)track_times.size() * 4 ) ); + RETURN_ERR( in.read( track_times.begin(), track_times.size() * 4 ) ); break; case BLARGG_4CHAR('l','b','l','t'): @@ -242,7 +243,7 @@ blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const int remapped = remap_track( track ); if ( (unsigned) remapped < track_times.size() ) { - long length = (BOOST::int32_t) get_le32( track_times [remapped] ); + long length = (int32_t) get_le32( track_times [remapped] ); if ( length > 0 ) out->length = length; } @@ -300,7 +301,7 @@ static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; } static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; } static gme_type_t_ const gme_nsfe_type_ = { "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 }; -gme_type_t const gme_nsfe_type = &gme_nsfe_type_; +BLARGG_EXPORT extern gme_type_t const gme_nsfe_type = &gme_nsfe_type_; blargg_err_t Nsfe_Emu::load_( Data_Reader& in ) diff --git a/game-music-emu/gme/Nsfe_Emu.h b/game-music-emu/gme/Nsfe_Emu.h index 32b05d55e..fd65f0af8 100644 --- a/game-music-emu/gme/Nsfe_Emu.h +++ b/game-music-emu/gme/Nsfe_Emu.h @@ -1,6 +1,6 @@ // Nintendo NES/Famicom NSFE music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef NSFE_EMU_H #define NSFE_EMU_H diff --git a/game-music-emu/gme/Sap_Apu.cpp b/game-music-emu/gme/Sap_Apu.cpp index daf72ed4a..26fa2d13f 100644 --- a/game-music-emu/gme/Sap_Apu.cpp +++ b/game-music-emu/gme/Sap_Apu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Sap_Apu.h" @@ -30,7 +30,7 @@ static void gen_poly( blargg_ulong mask, int count, byte* out ) { // implemented using "Galios configuration" bits |= (n & 1) << b; - n = (n >> 1) ^ (mask & (blargg_ulong)-(blargg_long)(n & 1)); + n = (n >> 1) ^ (mask & -(n & 1)); } while ( b++ < 7 ); *out++ = bits; diff --git a/game-music-emu/gme/Sap_Apu.h b/game-music-emu/gme/Sap_Apu.h index 8cdd7e50c..1b67571bc 100644 --- a/game-music-emu/gme/Sap_Apu.h +++ b/game-music-emu/gme/Sap_Apu.h @@ -1,6 +1,6 @@ // Atari POKEY sound chip emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SAP_APU_H #define SAP_APU_H diff --git a/game-music-emu/gme/Sap_Cpu.cpp b/game-music-emu/gme/Sap_Cpu.cpp index fd9112769..76ae277ad 100644 --- a/game-music-emu/gme/Sap_Cpu.cpp +++ b/game-music-emu/gme/Sap_Cpu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Sap_Cpu.h" @@ -68,27 +68,21 @@ void Sap_Cpu::reset( void* new_mem ) #define GET_SP() ((sp - 1) & 0xFF) #define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) -// even on x86, using short and unsigned char was slower -typedef int fint16; -typedef unsigned fuint16; -typedef unsigned fuint8; -typedef blargg_long fint32; - bool Sap_Cpu::run( sap_time_t end_time ) { bool illegal_encountered = false; set_end_time( end_time ); state_t s = this->state_; this->state = &s; - fint32 s_time = s.time; + int32_t s_time = s.time; uint8_t* const mem = this->mem; // cache // registers - fuint16 pc = r.pc; - fuint8 a = r.a; - fuint8 x = r.x; - fuint8 y = r.y; - fuint16 sp; + uint16_t pc = r.pc; + uint8_t a = r.a; + uint8_t x = r.x; + uint8_t y = r.y; + uint16_t sp; SET_SP( r.sp ); // status flags @@ -108,11 +102,11 @@ bool Sap_Cpu::run( sap_time_t end_time ) nz |= ~in & st_z;\ } while ( 0 ) - fuint8 status; - fuint16 c; // carry set if (c & 0x100) != 0 - fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + uint8_t status; + uint16_t c; // carry set if (c & 0x100) != 0 + uint16_t nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 { - fuint8 temp = r.status; + uint8_t temp = r.status; SET_STATUS( temp ); } @@ -135,7 +129,7 @@ loop: check( (unsigned) x < 0x100 ); check( (unsigned) y < 0x100 ); - fuint8 opcode = mem [pc]; + uint8_t opcode = mem [pc]; pc++; uint8_t const* instr = mem + pc; @@ -159,7 +153,7 @@ loop: 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F }; // 0x00 was 7 - fuint16 data; + uint16_t data; data = clock_table [opcode]; if ( (s_time += data) >= 0 ) goto possibly_out_of_time; @@ -191,13 +185,13 @@ possibly_out_of_time: #define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; #define IND_Y( cross, out ) {\ - fuint16 temp = READ_LOW( data ) + y;\ + uint16_t temp = READ_LOW( data ) + y;\ out = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\ cross( temp );\ } #define IND_X( out ) {\ - fuint16 temp = data + x;\ + uint16_t temp = data + x;\ out = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) );\ } @@ -232,8 +226,8 @@ imm##op: // TODO: more efficient way to handle negative branch that wraps PC around #define BRANCH( cond )\ {\ - fint16 offset = (BOOST::int8_t) data;\ - fuint16 extra_clock = (++pc & 0xFF) + offset;\ + int16_t offset = (int8_t) data;\ + uint16_t extra_clock = (++pc & 0xFF) + offset;\ if ( !(cond) ) goto dec_clock_loop;\ pc += offset;\ s_time += extra_clock >> 8 & 1;\ @@ -256,7 +250,7 @@ imm##op: BRANCH( (uint8_t) nz ); case 0x20: { // JSR - fuint16 temp = pc + 1; + uint16_t temp = pc + 1; pc = GET_ADDR(); WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); sp = (sp - 2) | 0x100; @@ -322,7 +316,7 @@ imm##op: goto loop; { - fuint16 addr; + uint16_t addr; case 0x99: // STA abs,Y addr = y + GET_ADDR(); @@ -378,7 +372,7 @@ imm##op: // common read instructions { - fuint16 addr; + uint16_t addr; case 0xA1: // LDA (ind,X) IND_X( addr ) @@ -494,7 +488,7 @@ imm##op: } { - fuint8 temp; + uint8_t temp; case 0x8C: // STY abs temp = y; goto store_abs; @@ -604,8 +598,8 @@ imm##op: ARITH_ADDR_MODES( 0x65 ) // ADC adc_imm: { check( !(status & st_d) ); - fint16 carry = c >> 8 & 1; - fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + int16_t carry = c >> 8 & 1; + int16_t ov = (a ^ 0x80) + carry + (int8_t) data; // sign-extend status &= ~st_v; status |= ov >> 2 & 0x40; c = nz = a + data + carry; @@ -633,7 +627,7 @@ imm##op: case 0x2A: { // ROL A nz = a << 1; - fint16 temp = c >> 8 & 1; + int16_t temp = c >> 8 & 1; c = nz; nz |= temp; a = (uint8_t) nz; @@ -725,7 +719,7 @@ imm##op: case 0xD6: // DEC zp,x data = uint8_t (data + x); case 0xC6: // DEC zp - nz = (unsigned) -1; + nz = (uint16_t) -1; add_nz_zp: nz += READ_LOW( data ); write_nz_zp: @@ -750,7 +744,7 @@ imm##op: case 0xCE: // DEC abs data = GET_ADDR(); dec_ptr: - nz = (unsigned) -1; + nz = (uint16_t) -1; inc_common: FLUSH_TIME(); nz += READ( data ); @@ -791,7 +785,7 @@ imm##op: goto loop; case 0x40:{// RTI - fuint8 temp = READ_LOW( sp ); + uint8_t temp = READ_LOW( sp ); pc = READ_LOW( 0x100 | (sp - 0xFF) ); pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; sp = (sp - 0xFD) | 0x100; @@ -811,9 +805,9 @@ imm##op: } case 0x28:{// PLP - fuint8 temp = READ_LOW( sp ); + uint8_t temp = READ_LOW( sp ); sp = (sp - 0xFF) | 0x100; - fuint8 changed = status ^ temp; + uint8_t changed = status ^ temp; SET_STATUS( temp ); if ( !(changed & st_i) ) goto loop; // I flag didn't change @@ -823,7 +817,7 @@ imm##op: } case 0x08: { // PHP - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); PUSH( temp | (st_b | st_r) ); goto loop; @@ -843,7 +837,7 @@ imm##op: // Flags case 0x38: // SEC - c = (unsigned) ~0; + c = (uint16_t) ~0; goto loop; case 0x18: // CLC @@ -932,7 +926,6 @@ imm##op: //case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2: default: - assert( (unsigned) opcode <= 0xFF ); illegal_encountered = true; pc--; goto stop; @@ -956,7 +949,7 @@ interrupt: pc = GET_LE16( &READ_PROG( 0xFFFA ) + result_ ); sp = (sp - 3) | 0x100; - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); temp |= st_r; if ( result_ ) @@ -998,7 +991,7 @@ stop: r.y = y; { - fuint8 temp; + uint8_t temp; CALC_STATUS( temp ); r.status = temp; } diff --git a/game-music-emu/gme/Sap_Cpu.h b/game-music-emu/gme/Sap_Cpu.h index ea42c7a80..fdfb9a310 100644 --- a/game-music-emu/gme/Sap_Cpu.h +++ b/game-music-emu/gme/Sap_Cpu.h @@ -1,6 +1,6 @@ // Atari 6502 CPU emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SAP_CPU_H #define SAP_CPU_H @@ -12,8 +12,6 @@ enum { future_sap_time = INT_MAX / 2 + 1 }; class Sap_Cpu { public: - typedef BOOST::uint8_t uint8_t; - // Clear all registers and keep pointer to 64K memory passed in void reset( void* mem_64k ); @@ -23,12 +21,12 @@ public: // Registers are not updated until run() returns (except I flag in status) struct registers_t { - BOOST::uint16_t pc; - BOOST::uint8_t a; - BOOST::uint8_t x; - BOOST::uint8_t y; - BOOST::uint8_t status; - BOOST::uint8_t sp; + uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; }; registers_t r; diff --git a/game-music-emu/gme/Sap_Emu.cpp b/game-music-emu/gme/Sap_Emu.cpp index 31bce6a90..dc5d666d6 100644 --- a/game-music-emu/gme/Sap_Emu.cpp +++ b/game-music-emu/gme/Sap_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Sap_Emu.h" @@ -119,7 +119,7 @@ static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out char const* tag = (char const*) in; while ( in < line_end && *in > ' ' ) in++; - int tag_len = int((char const*) in - tag); + int tag_len = (char const*) in - tag; while ( in < line_end && *in <= ' ' ) in++; @@ -236,8 +236,7 @@ static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; } static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; } static gme_type_t_ const gme_sap_type_ = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 }; -gme_type_t const gme_sap_type = &gme_sap_type_; - +BLARGG_EXPORT extern gme_type_t const gme_sap_type = &gme_sap_type_; // Setup @@ -256,7 +255,7 @@ blargg_err_t Sap_Emu::load_mem_( byte const* in, long size ) set_warning( info.warning ); set_track_count( info.track_count ); - set_voice_count( Sap_Apu::osc_count << (int)info.stereo ); + set_voice_count( Sap_Apu::osc_count << info.stereo ); apu_impl.volume( gain() ); return setup_buffer( 1773447 ); @@ -315,8 +314,8 @@ inline void Sap_Emu::call_init( int track ) case 'C': r.a = 0x70; - r.x = (BOOST::uint8_t)(info.music_addr&0xFF); - r.y = (BOOST::uint8_t)(info.music_addr >> 8); + r.x = info.music_addr&0xFF; + r.y = info.music_addr >> 8; run_routine( info.play_addr + 3 ); r.a = 0; r.x = track; diff --git a/game-music-emu/gme/Sap_Emu.h b/game-music-emu/gme/Sap_Emu.h index 5b0dc47df..f75312713 100644 --- a/game-music-emu/gme/Sap_Emu.h +++ b/game-music-emu/gme/Sap_Emu.h @@ -1,6 +1,6 @@ // Atari XL/XE SAP music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SAP_EMU_H #define SAP_EMU_H @@ -54,8 +54,7 @@ private: // large items struct { byte padding1 [0x100]; - byte ram [0x10000]; - byte padding2 [0x100]; + byte ram [0x10000 + 0x100]; } mem; Sap_Apu_Impl apu_impl; diff --git a/game-music-emu/gme/Sms_Apu.cpp b/game-music-emu/gme/Sms_Apu.cpp index be226f8f9..b41fdec41 100644 --- a/game-music-emu/gme/Sms_Apu.cpp +++ b/game-music-emu/gme/Sms_Apu.cpp @@ -141,7 +141,7 @@ void Sms_Noise::run( blip_time_t time, blip_time_t end_time ) do { int changed = shifter + 1; - shifter = (feedback & (unsigned)-(signed)(shifter & 1)) ^ (shifter >> 1); + shifter = (feedback & -(shifter & 1)) ^ (shifter >> 1); if ( changed & 2 ) // true if bits 0 and 1 differ { delta = -delta; diff --git a/game-music-emu/gme/Snes_Spc.cpp b/game-music-emu/gme/Snes_Spc.cpp index 26574223d..0b2077d8c 100644 --- a/game-music-emu/gme/Snes_Spc.cpp +++ b/game-music-emu/gme/Snes_Spc.cpp @@ -1,6 +1,6 @@ // SPC emulation support: init, sample buffering, reset, SPC loading -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Snes_Spc.h" @@ -143,8 +143,8 @@ void Snes_Spc::ram_loaded() load_regs( &RAM [0xF0] ); // Put STOP instruction around memory to catch PC underflow/overflow - memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 ); - memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 ); + memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 ); + memset( m.ram.ram + 0x10000, cpu_pad_fill, sizeof m.ram.padding1 ); } // Registers were just loaded. Applies these new values. @@ -303,7 +303,7 @@ void Snes_Spc::set_output( sample_t* out, int size ) assert( out <= out_end ); } - dsp.set_output( out, int(out_end - out) ); + dsp.set_output( out, out_end - out ); } else { diff --git a/game-music-emu/gme/Snes_Spc.h b/game-music-emu/gme/Snes_Spc.h index d6c4e8135..68c780ab7 100644 --- a/game-music-emu/gme/Snes_Spc.h +++ b/game-music-emu/gme/Snes_Spc.h @@ -1,16 +1,16 @@ // SNES SPC-700 APU emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SNES_SPC_H #define SNES_SPC_H #include "Spc_Dsp.h" #include "blargg_endian.h" +#include + struct Snes_Spc { public: - typedef BOOST::uint8_t uint8_t; - // Must be called once before using blargg_err_t init(); @@ -108,12 +108,12 @@ public: // TODO: document struct regs_t { - int pc; - int a; - int x; - int y; - int psw; - int sp; + uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t psw; + uint8_t sp; }; regs_t& smp_regs() { return m.cpu_regs; } @@ -123,8 +123,6 @@ public: public: BLARGG_DISABLE_NOTHROW - typedef BOOST::uint16_t uint16_t; - // Time relative to m_spc_time. Speeds up code a bit by eliminating need to // constantly add m_spc_time to time from CPU. CPU uses time that ends at // 0 to eliminate reloading end time every instruction. It pays off. @@ -184,13 +182,11 @@ private: struct { - // padding to neutralize address overflow - union { - uint8_t padding1 [0x100]; - uint16_t align; // makes compiler align data for 16-bit access - } padding1 [1]; - uint8_t ram [0x10000]; - uint8_t padding2 [0x100]; + // padding to neutralize address overflow -- but this is + // still undefined behavior! TODO: remove and instead properly + // guard usage of emulated memory + uint8_t padding1 [0x100]; + alignas(uint16_t) uint8_t ram [0x10000 + 0x100]; } ram; }; state_t m; @@ -226,13 +222,13 @@ private: Timer* run_timer ( Timer* t, rel_time_t ); int dsp_read ( rel_time_t ); void dsp_write ( int data, rel_time_t ); - void cpu_write_smp_reg_( int data, rel_time_t, int addr ); - void cpu_write_smp_reg ( int data, rel_time_t, int addr ); - void cpu_write_high ( int data, int i, rel_time_t ); - void cpu_write ( int data, int addr, rel_time_t ); + void cpu_write_smp_reg_( int data, rel_time_t, uint16_t addr ); + void cpu_write_smp_reg ( int data, rel_time_t, uint16_t addr ); + void cpu_write_high ( int data, uint8_t i ); + void cpu_write ( int data, uint16_t addr, rel_time_t ); int cpu_read_smp_reg ( int i, rel_time_t ); - int cpu_read ( int addr, rel_time_t ); - unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t ); + int cpu_read ( uint16_t addr, rel_time_t ); + unsigned CPU_mem_bit ( uint16_t pc, rel_time_t ); bool check_echo_access ( int addr ); uint8_t* run_until_( time_t end_time ); diff --git a/game-music-emu/gme/Spc_Cpu.cpp b/game-music-emu/gme/Spc_Cpu.cpp index db96ac390..998fe121b 100644 --- a/game-music-emu/gme/Spc_Cpu.cpp +++ b/game-music-emu/gme/Spc_Cpu.cpp @@ -1,6 +1,6 @@ // Core SPC emulation: CPU, timers, SMP registers, memory -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Snes_Spc.h" @@ -284,7 +284,7 @@ static unsigned char const glitch_probs [3] [256] = // If write isn't preceded by read, data has this added to it int const no_read_before_write = 0x2000; -void Snes_Spc::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) +void Snes_Spc::cpu_write_smp_reg_( int data, rel_time_t time, uint16_t addr ) { switch ( addr ) { @@ -385,7 +385,7 @@ void Snes_Spc::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) } } -void Snes_Spc::cpu_write_smp_reg( int data, rel_time_t time, int addr ) +void Snes_Spc::cpu_write_smp_reg( int data, rel_time_t time, uint16_t addr ) { if ( addr == r_dspdata ) // 99% dsp_write( data, time ); @@ -393,33 +393,23 @@ void Snes_Spc::cpu_write_smp_reg( int data, rel_time_t time, int addr ) cpu_write_smp_reg_( data, time, addr ); } -void Snes_Spc::cpu_write_high( int data, int i, rel_time_t time ) +void Snes_Spc::cpu_write_high( int data, uint8_t i ) { - if ( i < rom_size ) - { - m.hi_ram [i] = (uint8_t) data; - if ( m.rom_enabled ) - RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM - } - else - { - assert( RAM [i + rom_addr] == (uint8_t) data ); - RAM [i + rom_addr] = cpu_pad_fill; // restore overwritten padding - cpu_write( data, i + rom_addr - 0x10000, time ); - } + assert ( i < rom_size ); + m.hi_ram [i] = (uint8_t) data; + if ( m.rom_enabled ) + RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM } -int const bits_in_int = CHAR_BIT * sizeof (int); - -void Snes_Spc::cpu_write( int data, int addr, rel_time_t time ) +void Snes_Spc::cpu_write( int data, uint16_t addr, rel_time_t time ) { MEM_ACCESS( time, addr ) // RAM RAM [addr] = (uint8_t) data; - int reg = addr - 0xF0; - if ( reg >= 0 ) // 64% + if ( addr >= 0xF0 ) // 64% { + const uint16_t reg = addr - 0xF0; // $F0-$FF if ( reg < reg_count ) // 87% { @@ -437,12 +427,8 @@ void Snes_Spc::cpu_write( int data, int addr, rel_time_t time ) cpu_write_smp_reg( data, time, reg ); } // High mem/address wrap-around - else - { - reg -= rom_addr - 0xF0; - if ( reg >= 0 ) // 1% in IPL ROM area or address wrapped around - cpu_write_high( data, reg, time ); - } + else if ( addr >= rom_addr ) // 1% in IPL ROM area or address wrapped around + cpu_write_high( data, addr - rom_addr ); } } @@ -463,7 +449,7 @@ inline int Snes_Spc::cpu_read_smp_reg( int reg, rel_time_t time ) return result; } -int Snes_Spc::cpu_read( int addr, rel_time_t time ) +int Snes_Spc::cpu_read( uint16_t addr, rel_time_t time ) { MEM_ACCESS( time, addr ) @@ -507,7 +493,7 @@ int Snes_Spc::cpu_read( int addr, rel_time_t time ) // Prefix and suffix for CPU emulator function #define SPC_CPU_RUN_FUNC \ -BOOST::uint8_t* Snes_Spc::run_until_( time_t end_time )\ +uint8_t* Snes_Spc::run_until_( time_t end_time )\ {\ rel_time_t rel_time = m.spc_time - end_time;\ assert( rel_time <= 0 );\ @@ -527,7 +513,7 @@ BOOST::uint8_t* Snes_Spc::run_until_( time_t end_time )\ return ®S [r_cpuio0];\ } -#define cpu_lag_max (12 - 1) // DIV YA,X takes 12 clocks +int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks void Snes_Spc::end_frame( time_t end_time ) { diff --git a/game-music-emu/gme/Spc_Cpu.h b/game-music-emu/gme/Spc_Cpu.h index 8829e818b..2dd3e63c2 100644 --- a/game-music-emu/gme/Spc_Cpu.h +++ b/game-music-emu/gme/Spc_Cpu.h @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ /* Copyright (C) 2004-2007 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -66,62 +66,37 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define READ_DP( time, addr ) READ ( time, DP_ADDR( addr ) ) #define WRITE_DP( time, addr, data ) WRITE( time, DP_ADDR( addr ), data ) -#define READ_PROG16( addr ) GET_LE16( ram + (addr) ) +#define READ_PROG16( addr ) (RAM [(addr) & 0xffff] | (RAM [((addr) + 1) & 0xffff] << 8)) -#define SET_PC( n ) (pc = ram + (n)) -#define GET_PC() (int(pc - ram)) -#define READ_PC( pc ) (*(pc)) -#define READ_PC16( pc ) GET_LE16( pc ) +#define SET_PC( n ) (pc = n) +#define GET_PC() (pc) +#define READ_PC( pc ) (ram [pc]) +#define READ_PC16( pc ) READ_PROG16( pc ) -// TODO: remove non-wrapping versions? -#define SPC_NO_SP_WRAPAROUND 0 +#define SET_SP( v ) (sp = v) +#define GET_SP() ((uint8_t) (sp)) -#define SET_SP( v ) (sp = ram + 0x101 + ((uint8_t) v)) -#define GET_SP() (uint8_t (sp - 0x101 - ram)) - -#if SPC_NO_SP_WRAPAROUND -#define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) -#define PUSH( v ) (void) (*--sp = (uint8_t) (v)) -#define POP( out ) (void) ((out) = *sp++) - -#else #define PUSH16( data )\ {\ - int addr = int((sp -= 2) - ram);\ - if ( addr > 0x100 )\ - {\ - SET_LE16( sp, data );\ - }\ - else\ - {\ - ram [(uint8_t) addr + 0x100] = (uint8_t) data;\ - sp [1] = (uint8_t) (data >> 8);\ - sp += 0x100;\ - }\ + PUSH( (data & 0xff00) >> 8 );\ + PUSH( data & 0xff );\ } #define PUSH( data )\ {\ - *--sp = (uint8_t) (data);\ - if ( sp - ram == 0x100 )\ - sp += 0x100;\ + ram [0x100 + sp] = (uint8_t) (data);\ + --sp;\ } #define POP( out )\ {\ - out = *sp++;\ - if ( sp - ram == 0x201 )\ - {\ - out = sp [-0x101];\ - sp -= 0x100;\ - }\ + ++sp;\ + out = ram [0x100 + sp];\ } -#endif - #define MEM_BIT( rel ) CPU_mem_bit( pc, rel_time + rel ) -unsigned Snes_Spc::CPU_mem_bit( uint8_t const* pc, rel_time_t rel_time ) +unsigned Snes_Spc::CPU_mem_bit( uint16_t pc, rel_time_t rel_time ) { unsigned addr = READ_PC16( pc ); unsigned t = READ( 0, addr & 0x1FFF ) >> (addr >> 13); @@ -163,11 +138,11 @@ int const nz_neg_mask = 0x880; // either bit set indicates N flag set SPC_CPU_RUN_FUNC { uint8_t* const ram = RAM; - int a = m.cpu_regs.a; - int x = m.cpu_regs.x; - int y = m.cpu_regs.y; - uint8_t const* pc; - uint8_t* sp; + uint8_t a = m.cpu_regs.a; + uint8_t x = m.cpu_regs.x; + uint8_t y = m.cpu_regs.y; + uint16_t pc; + uint8_t sp; int psw; int c; int nz; @@ -183,7 +158,7 @@ SPC_CPU_RUN_FUNC // Main loop cbranch_taken_loop: - pc += *(BOOST::int8_t const*) pc; + pc += (int8_t) ram [pc]; inc_pc_loop: pc++; loop: @@ -195,7 +170,7 @@ loop: check( (unsigned) x < 0x100 ); check( (unsigned) y < 0x100 ); - opcode = *pc; + opcode = ram [pc]; if ( (rel_time += m.cycle_table [opcode]) > 0 ) goto out_of_time; @@ -218,7 +193,8 @@ loop: */ // TODO: if PC is at end of memory, this will get wrong operand (very obscure) - data = *++pc; + pc++; + data = ram [pc]; switch ( opcode ) { @@ -227,10 +203,10 @@ loop: #define BRANCH( cond )\ {\ pc++;\ - pc += (BOOST::int8_t) data;\ + pc += (int8_t) data;\ if ( cond )\ goto loop;\ - pc -= (BOOST::int8_t) data;\ + pc -= (int8_t) data;\ rel_time -= 2;\ goto loop;\ } @@ -249,23 +225,12 @@ loop: } case 0x6F:// RET - #if SPC_NO_SP_WRAPAROUND { - SET_PC( GET_LE16( sp ) ); - sp += 2; + uint8_t l, h; + POP( l ); + POP( h ); + SET_PC( l | (h << 8) ); } - #else - { - int addr = int(sp - ram); - SET_PC( GET_LE16( sp ) ); - sp += 2; - if ( addr < 0x1FF ) - goto loop; - - SET_PC( sp [-0x101] * 0x100 + ram [(uint8_t) addr + 0x100] ); - sp -= 0x100; - } - #endif goto loop; case 0xE4: // MOV a,dp @@ -294,8 +259,7 @@ loop: REGS [i] = (uint8_t) data; // Registers other than $F2 and $F4-$F7 - //if ( i != 2 && i != 4 && i != 5 && i != 6 && i != 7 ) - if ( ((~0x2F00 << (bits_in_int - 16)) << i) < 0 ) // 12% + if ( i != 2 && (i < 4 || i > 7)) // 12% cpu_write_smp_reg( data, rel_time, i ); } } @@ -504,7 +468,7 @@ loop: case op + 0x01: /* dp,dp */\ data = READ_DP( -3, data );\ case op + 0x10:{/*dp,imm*/\ - uint8_t const* addr2 = pc + 1;\ + uint16_t addr2 = pc + 1;\ pc += 2;\ addr = READ_PC( addr2 ) + dp;\ }\ @@ -878,7 +842,7 @@ loop: // 12. BRANCHING COMMANDS case 0x2F: // BRA rel - pc += (BOOST::int8_t) data; + pc += (int8_t) data; goto inc_pc_loop; case 0x30: // BMI @@ -1002,10 +966,12 @@ loop: { int temp; + uint8_t l, h; case 0x7F: // RET1 - temp = *sp; - SET_PC( GET_LE16( sp + 1 ) ); - SET_SP(GET_SP() + 3); + POP (temp); + POP (l); + POP (h); + SET_PC( l | (h << 8) ); goto set_psw; case 0x8E: // POP PSW POP( temp ); @@ -1180,11 +1146,8 @@ loop: case 0xFF:{// STOP // handle PC wrap-around - unsigned addr = GET_PC() - 1; - if ( addr >= 0x10000 ) + if ( pc == 0x0000 ) { - addr &= 0xFFFF; - SET_PC( addr ); debug_printf( "SPC: PC wrapped around\n" ); goto loop; } @@ -1199,14 +1162,12 @@ loop: } // switch assert( 0 ); // catch any unhandled instructions -} +} out_of_time: - rel_time -= m.cycle_table [*pc]; // undo partial execution of opcode + rel_time -= m.cycle_table [ ram [pc] ]; // undo partial execution of opcode stop: // Uncache registers - if ( GET_PC() >= 0x10000 ) - debug_printf( "SPC: PC wrapped around\n" ); m.cpu_regs.pc = (uint16_t) GET_PC(); m.cpu_regs.sp = ( uint8_t) GET_SP(); m.cpu_regs.a = ( uint8_t) a; diff --git a/game-music-emu/gme/Spc_Dsp.cpp b/game-music-emu/gme/Spc_Dsp.cpp index ac0baed6b..51556434d 100644 --- a/game-music-emu/gme/Spc_Dsp.cpp +++ b/game-music-emu/gme/Spc_Dsp.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Spc_Dsp.h" @@ -28,11 +28,11 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // TODO: add to blargg_endian.h -#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) +#define GET_LE16SA( addr ) ((int16_t) GET_LE16( addr )) #define GET_LE16A( addr ) GET_LE16( addr ) #define SET_LE16A( addr, data ) SET_LE16( addr, data ) -static BOOST::uint8_t const initial_regs [Spc_Dsp::register_count] = +static uint8_t const initial_regs [Spc_Dsp::register_count] = { 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, @@ -155,7 +155,7 @@ inline void Spc_Dsp::init_counter() // counters start out with this synchronization m.counters [0] = 1; m.counters [1] = 0; - m.counters [2] = -0x20; + m.counters [2] = -0x20u; m.counters [3] = 0x0B; int n = 2; @@ -498,8 +498,9 @@ void Spc_Dsp::run( int clock_count ) // Decode four samples for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) { - // Extract upper nybble and scale appropriately - int s = ((int16_t) nybbles >> right_shift) << left_shift; + // Extract upper nybble and scale appropriately. Every cast is + // necessary to maintain correctness and avoid undef behavior + int s = int16_t(uint16_t((int16_t) nybbles >> right_shift) << left_shift); // Apply IIR filter (8 is the most commonly used) int const filter = brr_header & 0x0C; diff --git a/game-music-emu/gme/Spc_Dsp.h b/game-music-emu/gme/Spc_Dsp.h index 3cec1bd91..b364f0845 100644 --- a/game-music-emu/gme/Spc_Dsp.h +++ b/game-music-emu/gme/Spc_Dsp.h @@ -1,6 +1,6 @@ // Fast SNES SPC-700 DSP emulator (about 3x speed of accurate one) -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SPC_DSP_H #define SPC_DSP_H @@ -8,8 +8,6 @@ struct Spc_Dsp { public: - typedef BOOST::uint8_t uint8_t; - // Setup // Initializes DSP and has it use the 64K RAM provided @@ -89,9 +87,6 @@ public: public: BLARGG_DISABLE_NOTHROW - typedef BOOST::int8_t int8_t; - typedef BOOST::int16_t int16_t; - enum { echo_hist_size = 8 }; enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; @@ -154,7 +149,7 @@ private: #include -inline int Spc_Dsp::sample_count() const { return int(m.out - m.out_begin); } +inline int Spc_Dsp::sample_count() const { return m.out - m.out_begin; } inline int Spc_Dsp::read( int addr ) const { diff --git a/game-music-emu/gme/Spc_Emu.cpp b/game-music-emu/gme/Spc_Emu.cpp index 341b53f9d..35086ce71 100644 --- a/game-music-emu/gme/Spc_Emu.cpp +++ b/game-music-emu/gme/Spc_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Spc_Emu.h" @@ -228,14 +228,14 @@ struct Spc_File : Gme_Info_ { RETURN_ERR( xid6.resize( xid6_size ) ); RETURN_ERR( in.skip( xid6_offset - Spc_Emu::header_size ) ); - RETURN_ERR( in.read( xid6.begin(), (long)xid6.size() ) ); + RETURN_ERR( in.read( xid6.begin(), xid6.size() ) ); } return 0; } blargg_err_t track_info_( track_info_t* out, int ) const { - get_spc_info( header, xid6.begin(), (long)xid6.size(), out ); + get_spc_info( header, xid6.begin(), xid6.size(), out ); return 0; } }; @@ -244,7 +244,7 @@ static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; } static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; } static gme_type_t_ const gme_spc_type_ = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 }; -gme_type_t const gme_spc_type = &gme_spc_type_; +BLARGG_EXPORT extern gme_type_t const gme_spc_type = &gme_spc_type_; // Setup @@ -299,6 +299,11 @@ blargg_err_t Spc_Emu::start_track_( int track ) RETURN_ERR( apu.load_spc( file_data, file_size ) ); filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) ); apu.clear_echo(); + track_info_t spc_info; + RETURN_ERR( track_info_( &spc_info, track ) ); + // Set a default track length, need a non-zero fadeout + if ( spc_info.length > 0 ) + set_fade ( spc_info.length, 50 ); return 0; } diff --git a/game-music-emu/gme/Spc_Emu.h b/game-music-emu/gme/Spc_Emu.h index 09063f123..76e1ac63d 100644 --- a/game-music-emu/gme/Spc_Emu.h +++ b/game-music-emu/gme/Spc_Emu.h @@ -1,6 +1,6 @@ // Super Nintendo SPC music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SPC_EMU_H #define SPC_EMU_H diff --git a/game-music-emu/gme/Spc_Filter.cpp b/game-music-emu/gme/Spc_Filter.cpp index 0f2d18a83..2cc77fc93 100644 --- a/game-music-emu/gme/Spc_Filter.cpp +++ b/game-music-emu/gme/Spc_Filter.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Spc_Filter.h" diff --git a/game-music-emu/gme/Spc_Filter.h b/game-music-emu/gme/Spc_Filter.h index 5cdcc3606..d9994af5f 100644 --- a/game-music-emu/gme/Spc_Filter.h +++ b/game-music-emu/gme/Spc_Filter.h @@ -1,6 +1,6 @@ // Simple low-pass and high-pass filter to better match sound output of a SNES -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef SPC_FILTER_H #define SPC_FILTER_H diff --git a/game-music-emu/gme/Vgm_Emu.cpp b/game-music-emu/gme/Vgm_Emu.cpp index 0f6001aae..8f19b7de5 100644 --- a/game-music-emu/gme/Vgm_Emu.cpp +++ b/game-music-emu/gme/Vgm_Emu.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Vgm_Emu.h" @@ -57,7 +57,7 @@ static byte const* skip_gd3_str( byte const* in, byte const* end ) static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) { byte const* mid = skip_gd3_str( in, end ); - int len = int(mid - in) / 2 - 1; + int len = (mid - in) / 2 - 1; if ( len > 0 ) { len = min( len, (int) Gme_File::max_field_ ); @@ -108,7 +108,7 @@ byte const* Vgm_Emu::gd3_data( int* size ) const return 0; byte const* gd3 = data + header_size + gd3_offset; - long gd3_size = check_gd3_header( gd3, long(data_end - gd3) ); + long gd3_size = check_gd3_header( gd3, data_end - gd3 ); if ( !gd3_size ) return 0; @@ -184,7 +184,7 @@ struct Vgm_File : Gme_Info_ if ( gd3_size ) { RETURN_ERR( gd3.resize( gd3_size ) ); - RETURN_ERR( in.read( gd3.begin(), (long)gd3.size() ) ); + RETURN_ERR( in.read( gd3.begin(), gd3.size() ) ); } } return 0; @@ -203,10 +203,10 @@ static Music_Emu* new_vgm_emu () { return BLARGG_NEW Vgm_Emu ; } static Music_Emu* new_vgm_file() { return BLARGG_NEW Vgm_File; } static gme_type_t_ const gme_vgm_type_ = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGM", 1 }; -gme_type_t const gme_vgm_type = &gme_vgm_type_; +BLARGG_EXPORT extern gme_type_t const gme_vgm_type = &gme_vgm_type_; static gme_type_t_ const gme_vgz_type_ = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGZ", 1 }; -gme_type_t const gme_vgz_type = &gme_vgz_type_; +BLARGG_EXPORT extern gme_type_t const gme_vgz_type = &gme_vgz_type_; // Setup @@ -233,6 +233,25 @@ blargg_err_t Vgm_Emu::set_sample_rate_( long sample_rate ) return Classic_Emu::set_sample_rate_( sample_rate ); } +blargg_err_t Vgm_Emu::set_multi_channel ( bool is_enabled ) +{ + // we acutally should check here whether this is classic emu or not + // however set_multi_channel() is called before setup_fm() resulting in uninited is_classic_emu() + // hard code it to unsupported +#if 0 + if ( is_classic_emu() ) + { + RETURN_ERR( Music_Emu::set_multi_channel_( is_enabled ) ); + return 0; + } + else +#endif + { + (void) is_enabled; + return "multichannel rendering not supported for YM2*** FM sound chip emulators"; + } +} + void Vgm_Emu::update_eq( blip_eq_t const& eq ) { psg.treble_eq( eq ); diff --git a/game-music-emu/gme/Vgm_Emu.h b/game-music-emu/gme/Vgm_Emu.h index 65895afaa..40cfb7102 100644 --- a/game-music-emu/gme/Vgm_Emu.h +++ b/game-music-emu/gme/Vgm_Emu.h @@ -1,6 +1,6 @@ // Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef VGM_EMU_H #define VGM_EMU_H @@ -17,6 +17,8 @@ public: // TODO: move into Music_Emu and rename to something like supports_custom_buffer() bool is_classic_emu() const { return !uses_fm; } + blargg_err_t set_multi_channel ( bool is_enabled ) override; + // Disable running FM chips at higher than normal rate. Will result in slightly // more aliasing of high notes. void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; } diff --git a/game-music-emu/gme/Vgm_Emu_Impl.cpp b/game-music-emu/gme/Vgm_Emu_Impl.cpp index 60dc099fe..0d400254d 100644 --- a/game-music-emu/gme/Vgm_Emu_Impl.cpp +++ b/game-music-emu/gme/Vgm_Emu_Impl.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Vgm_Emu.h" diff --git a/game-music-emu/gme/Vgm_Emu_Impl.h b/game-music-emu/gme/Vgm_Emu_Impl.h index b50094a01..dadbb9207 100644 --- a/game-music-emu/gme/Vgm_Emu_Impl.h +++ b/game-music-emu/gme/Vgm_Emu_Impl.h @@ -1,6 +1,6 @@ // Low-level parts of Vgm_Emu -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef VGM_EMU_IMPL_H #define VGM_EMU_IMPL_H diff --git a/game-music-emu/gme/Ym2413_Emu.cpp b/game-music-emu/gme/Ym2413_Emu.cpp index d1c7a71b1..01e796d95 100644 --- a/game-music-emu/gme/Ym2413_Emu.cpp +++ b/game-music-emu/gme/Ym2413_Emu.cpp @@ -1,7 +1,7 @@ // Use in place of Ym2413_Emu.cpp and ym2413.c to disable support for this chip -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Ym2413_Emu.h" diff --git a/game-music-emu/gme/Ym2413_Emu.h b/game-music-emu/gme/Ym2413_Emu.h index 53ce38a96..ed4fd11df 100644 --- a/game-music-emu/gme/Ym2413_Emu.h +++ b/game-music-emu/gme/Ym2413_Emu.h @@ -1,6 +1,6 @@ // YM2413 FM sound chip emulator interface -// Game_Music_Emu 0.6.0 +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #ifndef YM2413_EMU_H #define YM2413_EMU_H diff --git a/game-music-emu/gme/Ym2612_Emu.h b/game-music-emu/gme/Ym2612_Emu.h index 51cff6586..f62209a07 100644 --- a/game-music-emu/gme/Ym2612_Emu.h +++ b/game-music-emu/gme/Ym2612_Emu.h @@ -1,38 +1,19 @@ // YM2612 FM sound chip emulator interface -// Game_Music_Emu 0.6.0 -#ifndef YM2612_EMU_H -#define YM2612_EMU_H - -struct Ym2612_Impl; - -class Ym2612_Emu { - Ym2612_Impl* impl; -public: - Ym2612_Emu() { impl = 0; } - ~Ym2612_Emu(); - - // Set output sample rate and chip clock rates, in Hz. Returns non-zero - // if error. - const char* set_rate( double sample_rate, double clock_rate ); - - // Reset to power-up state - void reset(); - - // Mute voice n if bit n (1 << n) of mask is set - enum { channel_count = 6 }; - void mute_voices( int mask ); - - // Write addr to register 0 then data to register 1 - void write0( int addr, int data ); - - // Write addr to register 2 then data to register 3 - void write1( int addr, int data ); - - // Run and add pair_count samples into current output buffer contents - typedef short sample_t; - enum { out_chan_count = 2 }; // stereo - void run( int pair_count, sample_t* out ); -}; +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ +#ifdef VGM_YM2612_GENS // LGPL v2.1+ license +#include "Ym2612_GENS.h" +typedef Ym2612_GENS_Emu Ym2612_Emu; #endif + +#ifdef VGM_YM2612_NUKED // LGPL v2.1+ license +#include "Ym2612_Nuked.h" +typedef Ym2612_Nuked_Emu Ym2612_Emu; +#endif + +#ifdef VGM_YM2612_MAME // GPL v2+ license +#include "Ym2612_MAME.h" +typedef Ym2612_MAME_Emu Ym2612_Emu; +#endif + diff --git a/game-music-emu/gme/Ym2612_Emu.cpp b/game-music-emu/gme/Ym2612_GENS.cpp similarity index 92% rename from game-music-emu/gme/Ym2612_Emu.cpp rename to game-music-emu/gme/Ym2612_GENS.cpp index 4f9d84271..d9930d62b 100644 --- a/game-music-emu/gme/Ym2612_Emu.cpp +++ b/game-music-emu/gme/Ym2612_GENS.cpp @@ -1,8 +1,8 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ // Based on Gens 2.10 ym2612.c -#include "Ym2612_Emu.h" +#include "Ym2612_GENS.h" #include #include @@ -11,7 +11,7 @@ #include #include -/* Copyright (C) 2002 Stephane Dallongeville (gens AT consolemul.com) */ +/* Copyright (C) 2002 Stéphane Dallongeville (gens AT consolemul.com) */ /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -44,9 +44,9 @@ struct slot_t int MUL; // parametre "multiple de frequence" int TL; // Total Level = volume lorsque l'enveloppe est au plus haut int TLL; // Total Level ajusted - int SLL; // Sustin Level (ajusted) = volume oEl'enveloppe termine sa premiere phase de regression + int SLL; // Sustin Level (ajusted) = volume où l'enveloppe termine sa premiere phase de regression int KSR_S; // Key Scale Rate Shift = facteur de prise en compte du KSL dans la variations de l'enveloppe - int KSR; // Key Scale Rate = cette valeur est calculee par rapport Ela frequence actuelle, elle va influer + int KSR; // Key Scale Rate = cette valeur est calculee par rapport à la frequence actuelle, elle va influer // sur les differents parametres de l'enveloppe comme l'attaque, le decay ... comme dans la realite ! int SEG; // Type enveloppe SSG int env_xor; @@ -58,24 +58,24 @@ struct slot_t const int *RR; // Release Rate (table pointeur) = Taux pour le rel'chement (RR[KSR]) int Fcnt; // Frequency Count = compteur-frequence pour determiner l'amplitude actuelle (SIN[Finc >> 16]) int Finc; // frequency step = pas d'incrementation du compteur-frequence - // plus le pas est grand, plus la frequence est aEu (ou haute) + // plus le pas est grand, plus la frequence est aïgu (ou haute) int Ecurp; // Envelope current phase = cette variable permet de savoir dans quelle phase // de l'enveloppe on se trouve, par exemple phase d'attaque ou phase de maintenue ... // en fonction de la valeur de cette variable, on va appeler une fonction permettant - // de mettre Ejour l'enveloppe courante. - int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir oEl'on se trouve dans l'enveloppe + // de mettre à jour l'enveloppe courante. + int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir où l'on se trouve dans l'enveloppe int Einc; // Envelope step courant int Ecmp; // Envelope counter limite pour la prochaine phase int EincA; // Envelope step for Attack = pas d'incrementation du compteur durant la phase d'attaque - // cette valeur est egal EAR[KSR] + // cette valeur est egal à AR[KSR] int EincD; // Envelope step for Decay = pas d'incrementation du compteur durant la phase de regression - // cette valeur est egal EDR[KSR] + // cette valeur est egal à DR[KSR] int EincS; // Envelope step for Sustain = pas d'incrementation du compteur durant la phase de maintenue - // cette valeur est egal ESR[KSR] + // cette valeur est egal à SR[KSR] int EincR; // Envelope step for Release = pas d'incrementation du compteur durant la phase de rel'chement - // cette valeur est egal ERR[KSR] - int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot El'entree - // d'un autre ou carrement Ela sortie de la voie + // cette valeur est egal à RR[KSR] + int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot à l'entree + // d'un autre ou carrement à la sortie de la voie int INd; // input data of the slot = donnees en entree du slot int ChgEnM; // Change envelop mask. int AMS; // AMS depth level of this SLOT = degre de modulation de l'amplitude par le LFO @@ -102,15 +102,15 @@ struct state_t { int TimerBase; // TimerBase calculation int Status; // YM2612 Status (timer overflow) - int TimerA; // timerA limit = valeur jusqu'Elaquelle le timer A doit compter + int TimerA; // timerA limit = valeur jusqu'à laquelle le timer A doit compter int TimerAL; int TimerAcnt; // timerA counter = valeur courante du Timer A - int TimerB; // timerB limit = valeur jusqu'Elaquelle le timer B doit compter + int TimerB; // timerB limit = valeur jusqu'à laquelle le timer B doit compter int TimerBL; int TimerBcnt; // timerB counter = valeur courante du Timer B int Mode; // Mode actuel des voie 3 et 6 (normal / special) int DAC; // DAC enabled flag - channel_t CHANNEL[Ym2612_Emu::channel_count]; // Les 6 voies du YM2612 + channel_t CHANNEL[Ym2612_GENS_Emu::channel_count]; // Les 6 voies du YM2612 int REG[2][0x100]; // Sauvegardes des valeurs de tout les registres, c'est facultatif // cela nous rend le debuggage plus facile }; @@ -203,9 +203,9 @@ struct tables_t unsigned int SL_TAB [16]; // Substain level table unsigned int NULL_RATE [32]; // Table for NULL rate int LFO_INC_TAB [8]; // LFO step table - + short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) - + short LFO_ENV_TAB [LFO_LENGHT]; // LFO AMS TABLE (adjusted for 11.8 dB) short LFO_FREQ_TAB [LFO_LENGHT]; // LFO FMS TABLE int TL_TAB [TL_LENGHT * 2]; // TOTAL LEVEL TABLE (positif and minus) @@ -233,7 +233,7 @@ static const unsigned char DT_DEF_TAB [4 * 32] = }; static const unsigned char FKEY_TAB [16] = -{ +{ 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, @@ -255,38 +255,38 @@ static const unsigned char LFO_FMS_TAB [8] = inline void YM2612_Special_Update() { } -struct Ym2612_Impl +struct Ym2612_GENS_Impl { - enum { channel_count = Ym2612_Emu::channel_count }; - + enum { channel_count = Ym2612_GENS_Emu::channel_count }; + state_t YM2612; int mute_mask; tables_t g; - + void KEY_ON( channel_t&, int ); void KEY_OFF( channel_t&, int ); int SLOT_SET( int, int ); int CHANNEL_SET( int, int ); int YM_SET( int, int ); - + void set_rate( double sample_rate, double clock_factor ); void reset(); void write0( int addr, int data ); void write1( int addr, int data ); void run_timer( int ); - void run( int pair_count, Ym2612_Emu::sample_t* ); + void run( int pair_count, Ym2612_GENS_Emu::sample_t* ); }; -void Ym2612_Impl::KEY_ON( channel_t& ch, int nsl) +void Ym2612_GENS_Impl::KEY_ON( channel_t& ch, int nsl) { slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot - + if (SL->Ecurp == RELEASE) // la touche est-elle rel'chee ? { SL->Fcnt = 0; // Fix Ecco 2 splash sound - + SL->Ecnt = (g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK) & SL->ChgEnM; SL->ChgEnM = ~0; @@ -300,10 +300,10 @@ void Ym2612_Impl::KEY_ON( channel_t& ch, int nsl) } -void Ym2612_Impl::KEY_OFF(channel_t& ch, int nsl) +void Ym2612_GENS_Impl::KEY_OFF(channel_t& ch, int nsl) { slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot - + if (SL->Ecurp != RELEASE) // la touche est-elle appuyee ? { if (SL->Ecnt < ENV_DECAY) // attack phase ? @@ -318,12 +318,12 @@ void Ym2612_Impl::KEY_OFF(channel_t& ch, int nsl) } -int Ym2612_Impl::SLOT_SET( int Adr, int data ) +int Ym2612_GENS_Impl::SLOT_SET( int Adr, int data ) { int nch = Adr & 3; if ( nch == 3 ) return 1; - + channel_t& ch = YM2612.CHANNEL [nch + (Adr & 0x100 ? 3 : 0)]; slot_t& sl = ch.SLOT [(Adr >> 2) & 3]; @@ -397,7 +397,7 @@ int Ym2612_Impl::SLOT_SET( int Adr, int data ) // SSG-EG envelope shapes : /* E At Al H - + 1 0 0 0 \\\\ 1 0 0 1 \___ 1 0 1 0 \/\/ @@ -406,7 +406,7 @@ int Ym2612_Impl::SLOT_SET( int Adr, int data ) 1 1 0 1 / 1 1 1 0 /\/\ 1 1 1 1 /___ - + E = SSG-EG enable At = Start negate Al = Altern @@ -420,14 +420,14 @@ int Ym2612_Impl::SLOT_SET( int Adr, int data ) } -int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) +int Ym2612_GENS_Impl::CHANNEL_SET( int Adr, int data ) { int num = Adr & 3; if ( num == 3 ) return 1; - + channel_t& ch = YM2612.CHANNEL [num + (Adr & 0x100 ? 3 : 0)]; - + switch ( Adr & 0xFC ) { case 0xA0: @@ -487,7 +487,7 @@ int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) YM2612_Special_Update(); ch.ALGO = data & 7; - + ch.SLOT [0].ChgEnM = 0; ch.SLOT [1].ChgEnM = 0; ch.SLOT [2].ChgEnM = 0; @@ -502,13 +502,13 @@ int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) case 0xB4: { YM2612_Special_Update(); - + ch.LEFT = 0 - ((data >> 7) & 1); ch.RIGHT = 0 - ((data >> 6) & 1); - + ch.AMS = LFO_AMS_TAB [(data >> 4) & 3]; ch.FMS = LFO_FMS_TAB [data & 7]; - + for ( int i = 0; i < 4; i++ ) { slot_t& sl = ch.SLOT [i]; @@ -517,12 +517,12 @@ int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) break; } } - + return 0; } -int Ym2612_Impl::YM_SET(int Adr, int data) +int Ym2612_GENS_Impl::YM_SET(int Adr, int data) { switch ( Adr ) { @@ -617,27 +617,27 @@ int Ym2612_Impl::YM_SET(int Adr, int data) else KEY_OFF(ch, S3); // On rel'che la touche pour le slot 4 break; } - + case 0x2B: if (YM2612.DAC ^ (data & 0x80)) YM2612_Special_Update(); YM2612.DAC = data & 0x80; // activation/desactivation du DAC break; } - + return 0; } -void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) +void Ym2612_GENS_Impl::set_rate( double sample_rate, double clock_rate ) { assert( sample_rate ); assert( clock_rate > sample_rate ); - + int i; // 144 = 12 * (prescale * 2) = 12 * 6 * 2 // prescale set to 6 by default - + double Frequence = clock_rate / sample_rate / 144.0; if ( fabs( Frequence - 1.0 ) < 0.0000001 ) Frequence = 1.0; @@ -662,9 +662,9 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) g.TL_TAB [TL_LENGHT + i] = -g.TL_TAB [i]; } } - + // Tableau SIN : - // g.SIN_TAB [x] [y] = sin(x) * y; + // g.SIN_TAB [x] [y] = sin(x) * y; // x = phase and y = volume g.SIN_TAB [0] = g.SIN_TAB [SIN_LENGHT / 2] = PG_CUT_OFF; @@ -720,11 +720,11 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) } for ( i = 0; i < 8; i++ ) g.ENV_TAB [i + ENV_LENGHT * 2] = 0; - + g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state - + // Tableau pour la conversion Attack -> Decay and Decay -> Attack - + int j = ENV_LENGHT - 1; for ( i = 0; i < ENV_LENGHT; i++ ) { @@ -735,7 +735,7 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) } // Tableau pour le Substain Level - + for(i = 0; i < 15; i++) { double x = i * 3; // 3 and not 6 (Mickey Mania first music for test) @@ -770,7 +770,7 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) g.AR_TAB [i] = 0; g.DR_TAB [i] = 0; } - + for(i = 0; i < 60; i++) { double x = Frequence; @@ -790,10 +790,10 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) g.NULL_RATE [i - 64] = 0; } - + for ( i = 96; i < 128; i++ ) g.AR_TAB [i] = 0; - + // Tableau Detune for(i = 0; i < 4; i++) @@ -810,7 +810,7 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) g.DT_TAB [i + 4] [j] = (int) -y; } } - + // Tableau LFO g.LFO_INC_TAB [0] = (unsigned int) (3.98 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); g.LFO_INC_TAB [1] = (unsigned int) (5.56 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); @@ -820,35 +820,35 @@ void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) g.LFO_INC_TAB [5] = (unsigned int) (9.63 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); g.LFO_INC_TAB [6] = (unsigned int) (48.1 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); g.LFO_INC_TAB [7] = (unsigned int) (72.2 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); - + reset(); } -const char* Ym2612_Emu::set_rate( double sample_rate, double clock_rate ) +const char* Ym2612_GENS_Emu::set_rate( double sample_rate, double clock_rate ) { if ( !impl ) { - impl = (Ym2612_Impl*) malloc( sizeof *impl ); + impl = (Ym2612_GENS_Impl*) malloc( sizeof *impl ); if ( !impl ) return "Out of memory"; impl->mute_mask = 0; } memset( &impl->YM2612, 0, sizeof impl->YM2612 ); - + impl->set_rate( sample_rate, clock_rate ); - + return 0; } -Ym2612_Emu::~Ym2612_Emu() +Ym2612_GENS_Emu::~Ym2612_GENS_Emu() { free( impl ); } -inline void Ym2612_Impl::write0( int opn_addr, int data ) +inline void Ym2612_GENS_Impl::write0( int opn_addr, int data ) { assert( (unsigned) data <= 0xFF ); - + if ( opn_addr < 0x30 ) { YM2612.REG [0] [opn_addr] = data; @@ -857,7 +857,7 @@ inline void Ym2612_Impl::write0( int opn_addr, int data ) else if ( YM2612.REG [0] [opn_addr] != data ) { YM2612.REG [0] [opn_addr] = data; - + if ( opn_addr < 0xA0 ) SLOT_SET( opn_addr, data ); else @@ -865,10 +865,10 @@ inline void Ym2612_Impl::write0( int opn_addr, int data ) } } -inline void Ym2612_Impl::write1( int opn_addr, int data ) +inline void Ym2612_GENS_Impl::write1( int opn_addr, int data ) { assert( (unsigned) data <= 0xFF ); - + if ( opn_addr >= 0x30 && YM2612.REG [1] [opn_addr] != data ) { YM2612.REG [1] [opn_addr] = data; @@ -880,12 +880,12 @@ inline void Ym2612_Impl::write1( int opn_addr, int data ) } } -void Ym2612_Emu::reset() +void Ym2612_GENS_Emu::reset() { impl->reset(); } -void Ym2612_Impl::reset() +void Ym2612_GENS_Impl::reset() { g.LFOcnt = 0; YM2612.TimerA = 0; @@ -902,7 +902,7 @@ void Ym2612_Impl::reset() for ( i = 0; i < channel_count; i++ ) { channel_t& ch = YM2612.CHANNEL [i]; - + ch.LEFT = ~0; ch.RIGHT = ~0; ch.ALGO = 0; @@ -945,21 +945,21 @@ void Ym2612_Impl::reset() write0( i, 0 ); write1( i, 0 ); } - + write0( 0x2A, 0x80 ); } -void Ym2612_Emu::write0( int addr, int data ) +void Ym2612_GENS_Emu::write0( int addr, int data ) { impl->write0( addr, data ); } -void Ym2612_Emu::write1( int addr, int data ) +void Ym2612_GENS_Emu::write1( int addr, int data ) { impl->write1( addr, data ); } -void Ym2612_Emu::mute_voices( int mask ) { impl->mute_mask = mask; } +void Ym2612_GENS_Emu::mute_voices( int mask ) { impl->mute_mask = mask; } static void update_envelope_( slot_t* sl ) { @@ -967,7 +967,7 @@ static void update_envelope_( slot_t* sl ) { case 0: // Env_Attack_Next - + // Verified with Gynoug even in HQ (explode SFX) sl->Ecnt = ENV_DECAY; @@ -975,10 +975,10 @@ static void update_envelope_( slot_t* sl ) sl->Ecmp = sl->SLL; sl->Ecurp = DECAY; break; - + case 1: // Env_Decay_Next - + // Verified with Gynoug even in HQ (explode SFX) sl->Ecnt = sl->SLL; @@ -986,13 +986,13 @@ static void update_envelope_( slot_t* sl ) sl->Ecmp = ENV_END; sl->Ecurp = SUBSTAIN; break; - + case 2: // Env_Substain_Next(slot_t *SL) if (sl->SEG & 8) // SSG envelope type { int release = sl->SEG & 1; - + if ( !release ) { // re KEY ON @@ -1007,19 +1007,19 @@ static void update_envelope_( slot_t* sl ) } set_seg( *sl, (sl->SEG << 1) & 4 ); - + if ( !release ) break; } // fall through - + case 3: // Env_Release_Next sl->Ecnt = ENV_END; sl->Einc = 0; sl->Ecmp = ENV_END + 1; break; - + // default: no op } } @@ -1033,64 +1033,64 @@ inline void update_envelope( slot_t& sl ) template struct ym2612_update_chan { - static void func( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); + static void func( tables_t&, channel_t&, Ym2612_GENS_Emu::sample_t*, int ); }; -typedef void (*ym2612_update_chan_t)( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); +typedef void (*ym2612_update_chan_t)( tables_t&, channel_t&, Ym2612_GENS_Emu::sample_t*, int ); template void ym2612_update_chan::func( tables_t& g, channel_t& ch, - Ym2612_Emu::sample_t* buf, int length ) + Ym2612_GENS_Emu::sample_t* buf, int length ) { int not_end = ch.SLOT [S3].Ecnt - ENV_END; - + // algo is a compile-time constant, so all conditions based on it are resolved // during compilation - + // special cases if ( algo == 7 ) not_end |= ch.SLOT [S0].Ecnt - ENV_END; - + if ( algo >= 5 ) not_end |= ch.SLOT [S2].Ecnt - ENV_END; - + if ( algo >= 4 ) not_end |= ch.SLOT [S1].Ecnt - ENV_END; - + int CH_S0_OUT_1 = ch.S0_OUT [1]; - + int in0 = ch.SLOT [S0].Fcnt; int in1 = ch.SLOT [S1].Fcnt; int in2 = ch.SLOT [S2].Fcnt; int in3 = ch.SLOT [S3].Fcnt; - + int YM2612_LFOinc = g.LFOinc; int YM2612_LFOcnt = g.LFOcnt + YM2612_LFOinc; - + if ( !not_end ) return; - + do { // envelope int const env_LFO = g.LFO_ENV_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK]; - + short const* const ENV_TAB = g.ENV_TAB; - + #define CALC_EN( x ) \ int temp##x = ENV_TAB [ch.SLOT [S##x].Ecnt >> ENV_LBITS] + ch.SLOT [S##x].TLL; \ int en##x = ((temp##x ^ ch.SLOT [S##x].env_xor) + (env_LFO >> ch.SLOT [S##x].AMS)) & \ ((temp##x - ch.SLOT [S##x].env_max) >> 31); - + CALC_EN( 0 ) CALC_EN( 1 ) CALC_EN( 2 ) CALC_EN( 3 ) - + int const* const TL_TAB = g.TL_TAB; - + #define SINT( i, o ) (TL_TAB [g.SIN_TAB [(i)] + (o)]) - + // feedback int CH_S0_OUT_0 = ch.S0_OUT [0]; { @@ -1098,7 +1098,7 @@ void ym2612_update_chan::func( tables_t& g, channel_t& ch, CH_S0_OUT_1 = CH_S0_OUT_0; CH_S0_OUT_0 = SINT( (temp >> SIN_LBITS) & SIN_MASK, en0 ); } - + int CH_OUTd; if ( algo == 0 ) { @@ -1155,9 +1155,9 @@ void ym2612_update_chan::func( tables_t& g, channel_t& ch, SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ) + CH_S0_OUT_1; //DO_LIMIT } - + CH_OUTd >>= MAX_OUT_BITS - output_bits + 2; - + // update phase unsigned freq_LFO = ((g.LFO_FREQ_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK] * ch.FMS) >> (LFO_HBITS - 1 + 1)) + (1L << (LFO_FMS_LBITS - 1)); @@ -1166,24 +1166,24 @@ void ym2612_update_chan::func( tables_t& g, channel_t& ch, in1 += (ch.SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); - + int t0 = buf [0] + (CH_OUTd & ch.LEFT); int t1 = buf [1] + (CH_OUTd & ch.RIGHT); - + update_envelope( ch.SLOT [0] ); update_envelope( ch.SLOT [1] ); update_envelope( ch.SLOT [2] ); update_envelope( ch.SLOT [3] ); - + ch.S0_OUT [0] = CH_S0_OUT_0; buf [0] = t0; buf [1] = t1; buf += 2; } while ( --length ); - + ch.S0_OUT [1] = CH_S0_OUT_1; - + ch.SLOT [S0].Fcnt = in0; ch.SLOT [S1].Fcnt = in1; ch.SLOT [S2].Fcnt = in2; @@ -1201,7 +1201,7 @@ static const ym2612_update_chan_t UPDATE_CHAN [8] = { &ym2612_update_chan<7>::func }; -void Ym2612_Impl::run_timer( int length ) +void Ym2612_GENS_Impl::run_timer( int length ) { int const step = 6; int remain = length; @@ -1211,7 +1211,7 @@ void Ym2612_Impl::run_timer( int length ) if ( n > remain ) n = remain; remain -= n; - + long i = n * YM2612.TimerBase; if (YM2612.Mode & 1) // Timer A ON ? { @@ -1219,7 +1219,7 @@ void Ym2612_Impl::run_timer( int length ) if ((YM2612.TimerAcnt -= i) <= 0) { // timer a overflow - + YM2612.Status |= (YM2612.Mode & 0x04) >> 2; YM2612.TimerAcnt += YM2612.TimerAL; @@ -1247,37 +1247,37 @@ void Ym2612_Impl::run_timer( int length ) while ( remain > 0 ); } -void Ym2612_Impl::run( int pair_count, Ym2612_Emu::sample_t* out ) +void Ym2612_GENS_Impl::run( int pair_count, Ym2612_GENS_Emu::sample_t* out ) { if ( pair_count <= 0 ) return; - + if ( YM2612.Mode & 3 ) run_timer( pair_count ); - - // Mise Ejour des pas des compteurs-frequences s'ils ont ete modifies - + + // Mise à jour des pas des compteurs-frequences s'ils ont ete modifies + for ( int chi = 0; chi < channel_count; chi++ ) { channel_t& ch = YM2612.CHANNEL [chi]; if ( ch.SLOT [0].Finc != -1 ) continue; - + int i2 = 0; if ( chi == 2 && (YM2612.Mode & 0x40) ) i2 = 2; - + for ( int i = 0; i < 4; i++ ) { // static int seq [4] = { 2, 1, 3, 0 }; // if ( i2 ) i2 = seq [i]; - + slot_t& sl = ch.SLOT [i]; int finc = g.FINC_TAB [ch.FNUM [i2]] >> (7 - ch.FOCT [i2]); int ksr = ch.KC [i2] >> sl.KSR_S; // keycode attenuation sl.Finc = (finc + sl.DT [ch.KC [i2]]) * sl.MUL; if (sl.KSR != ksr) // si le KSR a change alors - { // les differents taux pour l'enveloppe sont mis Ejour + { // les differents taux pour l'enveloppe sont mis à jour sl.KSR = ksr; sl.EincA = sl.AR [ksr]; @@ -1301,19 +1301,19 @@ void Ym2612_Impl::run( int pair_count, Ym2612_Emu::sample_t* out ) sl.Einc = sl.EincR; } } - + if ( i2 ) i2 = (i2 ^ 2) ^ (i2 >> 1); } } - + for ( int i = 0; i < channel_count; i++ ) { if ( !(mute_mask & (1 << i)) && (i != 5 || !YM2612.DAC) ) UPDATE_CHAN [YM2612.CHANNEL [i].ALGO]( g, YM2612.CHANNEL [i], out, pair_count ); } - + g.LFOcnt += g.LFOinc * pair_count; } -void Ym2612_Emu::run( int pair_count, sample_t* out ) { impl->run( pair_count, out ); } +void Ym2612_GENS_Emu::run( int pair_count, sample_t* out ) { impl->run( pair_count, out ); } diff --git a/game-music-emu/gme/Ym2612_GENS.h b/game-music-emu/gme/Ym2612_GENS.h new file mode 100644 index 000000000..4cb2e8ae3 --- /dev/null +++ b/game-music-emu/gme/Ym2612_GENS.h @@ -0,0 +1,38 @@ +// YM2612 FM sound chip emulator interface + +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ +#ifndef YM2612_EMU_H +#define YM2612_EMU_H + +struct Ym2612_GENS_Impl; + +class Ym2612_GENS_Emu { + Ym2612_GENS_Impl* impl; +public: + Ym2612_GENS_Emu() { impl = 0; } + ~Ym2612_GENS_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + const char* set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 6 }; + void mute_voices( int mask ); + + // Write addr to register 0 then data to register 1 + void write0( int addr, int data ); + + // Write addr to register 2 then data to register 3 + void write1( int addr, int data ); + + // Run and add pair_count samples into current output buffer contents + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/game-music-emu/gme/Ym2612_MAME.cpp b/game-music-emu/gme/Ym2612_MAME.cpp new file mode 100644 index 000000000..524dab55a --- /dev/null +++ b/game-music-emu/gme/Ym2612_MAME.cpp @@ -0,0 +1,3108 @@ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ + +// Based on Mame YM2612 ym2612.c + +#include "Ym2612_MAME.h" + +/* +** +** File: fm2612.c -- software implementation of Yamaha YM2612 FM sound generator +** Split from fm.c to keep 2612 fixes from infecting other OPN chips +** +** Copyright Jarek Burczynski (bujar at mame dot net) +** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development +** +** Version 1.5.1 (Genesis Plus GX ym2612.c rev. 368) +** +*/ + +/* +** History: +** +** 2006~2012 Eke-Eke (Genesis Plus GX): +** Huge thanks to Nemesis, lot of those fixes came from his tests on Sega Genesis hardware +** More informations at http://gendev.spritesmind.net/forum/viewtopic.php?t=386 +** +** TODO: +** +** - core documentation +** - BUSY flag support +** +** CHANGELOG: +** +** 26-09-2017 Eke-Eke (Genesis Plus GX): +** - fixed EG counter loopback behavior (verified on YM3438 die) +** - reverted changes to EG rates 2-7 increment values +** +** xx-xx-xxxx +** - fixed LFO implementation: +** .added support for CH3 special mode: fixes various sound effects (birds in Warlock, bug sound in Aladdin...) +** .inverted LFO AM waveform: fixes Spider-Man & Venom : Separation Anxiety (intro), California Games (surfing event) +** .improved LFO timing accuracy: now updated AFTER sample output, like EG/PG updates, and without any precision loss anymore. +** - improved internal timers emulation +** - adjusted lowest EG rates increment values +** - fixed Attack Rate not being updated in some specific cases (Batman & Robin intro) +** - fixed EG behavior when Attack Rate is maximal +** - fixed EG behavior when SL=0 (Mega Turrican tracks 03,09...) or/and Key ON occurs at minimal attenuation +** - implemented EG output immediate changes on register writes +** - fixed YM2612 initial values (after the reset): fixes missing intro in B.O.B +** - implemented Detune overflow (Ariel, Comix Zone, Shaq Fu, Spiderman & many other games using GEMS sound engine) +** - implemented accurate CSM mode emulation +** - implemented accurate SSG-EG emulation (Asterix, Beavis&Butthead, Bubba'n Stix & many other games) +** - implemented accurate address/data ports behavior +** +** 06-23-2007 Zsolt Vasvari: +** - changed the timing not to require the use of floating point calculations +** +** 03-08-2003 Jarek Burczynski: +** - fixed YM2608 initial values (after the reset) +** - fixed flag and irqmask handling (YM2608) +** - fixed BUFRDY flag handling (YM2608) +** +** 14-06-2003 Jarek Burczynski: +** - implemented all of the YM2608 status register flags +** - implemented support for external memory read/write via YM2608 +** - implemented support for deltat memory limit register in YM2608 emulation +** +** 22-05-2003 Jarek Burczynski: +** - fixed LFO PM calculations (copy&paste bugfix) +** +** 08-05-2003 Jarek Burczynski: +** - fixed SSG support +** +** 22-04-2003 Jarek Burczynski: +** - implemented 100% correct LFO generator (verified on real YM2610 and YM2608) +** +** 15-04-2003 Jarek Burczynski: +** - added support for YM2608's register 0x110 - status mask +** +** 01-12-2002 Jarek Burczynski: +** - fixed register addressing in YM2608, YM2610, YM2610B chips. (verified on real YM2608) +** The addressing patch used for early Neo-Geo games can be removed now. +** +** 26-11-2002 Jarek Burczynski, Nicola Salmoria: +** - recreated YM2608 ADPCM ROM using data from real YM2608's output which leads to: +** - added emulation of YM2608 drums. +** - output of YM2608 is two times lower now - same as YM2610 (verified on real YM2608) +** +** 16-08-2002 Jarek Burczynski: +** - binary exact Envelope Generator (verified on real YM2203); +** identical to YM2151 +** - corrected 'off by one' error in feedback calculations (when feedback is off) +** - corrected connection (algorithm) calculation (verified on real YM2203 and YM2610) +** +** 18-12-2001 Jarek Burczynski: +** - added SSG-EG support (verified on real YM2203) +** +** 12-08-2001 Jarek Burczynski: +** - corrected sin_tab and tl_tab data (verified on real chip) +** - corrected feedback calculations (verified on real chip) +** - corrected phase generator calculations (verified on real chip) +** - corrected envelope generator calculations (verified on real chip) +** - corrected FM volume level (YM2610 and YM2610B). +** - changed YMxxxUpdateOne() functions (YM2203, YM2608, YM2610, YM2610B, YM2612) : +** this was needed to calculate YM2610 FM channels output correctly. +** (Each FM channel is calculated as in other chips, but the output of the channel +** gets shifted right by one *before* sending to accumulator. That was impossible to do +** with previous implementation). +** +** 23-07-2001 Jarek Burczynski, Nicola Salmoria: +** - corrected YM2610 ADPCM type A algorithm and tables (verified on real chip) +** +** 11-06-2001 Jarek Burczynski: +** - corrected end of sample bug in ADPCMA_calc_cha(). +** Real YM2610 checks for equality between current and end addresses (only 20 LSB bits). +** +** 08-12-98 hiro-shi: +** rename ADPCMA -> ADPCMB, ADPCMB -> ADPCMA +** move ROM limit check.(CALC_CH? -> 2610Write1/2) +** test program (ADPCMB_TEST) +** move ADPCM A/B end check. +** ADPCMB repeat flag(no check) +** change ADPCM volume rate (8->16) (32->48). +** +** 09-12-98 hiro-shi: +** change ADPCM volume. (8->16, 48->64) +** replace ym2610 ch0/3 (YM-2610B) +** change ADPCM_SHIFT (10->8) missing bank change 0x4000-0xffff. +** add ADPCM_SHIFT_MASK +** change ADPCMA_DECODE_MIN/MAX. +*/ + +/************************************************************************/ +/* comment of hiro-shi(Hiromitsu Shioya) */ +/* YM2610(B) = OPN-B */ +/* YM2610 : PSG:3ch FM:4ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ +/* YM2610B : PSG:3ch FM:6ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ +/************************************************************************/ + +#include +#include /* for memset */ +#include /* for NULL */ +#include +#include + +namespace Ym2612_MameImpl +{ + +/* ---- mamedef - begin ---- */ +/* typedefs to use MAME's (U)INTxx types (copied from MAME\src\ods\odscomm.h) */ +/* 8-bit values */ +typedef unsigned char UINT8; +typedef signed char INT8; + +/* 16-bit values */ +typedef unsigned short UINT16; +typedef signed short INT16; + +/* 32-bit values */ +#ifndef _WINDOWS_H +typedef unsigned int UINT32; +typedef signed int INT32; +#endif + +/* 64-bit values */ +#ifndef _WINDOWS_H +#ifdef _MSC_VER +typedef signed __int64 INT64; +typedef unsigned __int64 UINT64; +#else +__extension__ typedef unsigned long long UINT64; +__extension__ typedef signed long long INT64; +#endif +#endif + +/* offsets and addresses are 32-bit (for now...) */ +typedef UINT32 offs_t; + +/* stream_sample_t is used to represent a single sample in a sound stream */ +typedef INT16 stream_sample_t; + +#if defined(VGM_BIG_ENDIAN) +#define BYTE_XOR_BE(x) (x) +#elif defined(VGM_LITTLE_ENDIAN) +#define BYTE_XOR_BE(x) ((x) ^ 0x01) +#else +/* don't define BYTE_XOR_BE so that it throws an error when compiling */ +#endif + +#if defined(_MSC_VER) +//#define INLINE static __forceinline +#define INLINE static __inline +#elif defined(__GNUC__) +#define INLINE static __inline__ +#else +#define INLINE static inline +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef _DEBUG +#define logerror printf +#else +#define logerror +#endif + +typedef void (*SRATE_CALLBACK)(void*, UINT32); +/* ---- mamedef - end ---- */ + +/* --- select emulation chips --- */ +/* +#define BUILD_YM2203 (HAS_YM2203) // build YM2203(OPN) emulator +#define BUILD_YM2608 (HAS_YM2608) // build YM2608(OPNA) emulator +#define BUILD_YM2610 (HAS_YM2610) // build YM2610(OPNB) emulator +#define BUILD_YM2610B (HAS_YM2610B) // build YM2610B(OPNB?)emulator +#define BUILD_YM2612 (HAS_YM2612) // build YM2612(OPN2) emulator +#define BUILD_YM3438 (HAS_YM3438) // build YM3438(OPN) emulator +*/ +#define BUILD_YM2203 0 +#define BUILD_YM2608 0 +#define BUILD_YM2610 0 +#define BUILD_YM2610B 0 +#define BUILD_YM2612 1 +#define BUILD_YM3438 0 + +#define FM_BUSY_FLAG_SUPPORT 0 + +/* select bit size of output : 8 or 16 */ +#define FM_SAMPLE_BITS 16 + +/* select timer system internal or external */ +#define FM_INTERNAL_TIMER 1 + +/* --- speedup optimize --- */ +/* busy flag enulation , The definition of FM_GET_TIME_NOW() is necessary. */ +/* #define FM_BUSY_FLAG_SUPPORT 1 */ + +/* --- external SSG(YM2149/AY-3-8910)emulator interface port */ +/* used by YM2203,YM2608,and YM2610 */ +typedef struct _ssg_callbacks ssg_callbacks; +struct _ssg_callbacks +{ + void (*set_clock)(void *param, int clock); + void (*write)(void *param, int address, int data); + int (*read)(void *param); + void (*reset)(void *param); +}; + +/* --- external callback funstions for realtime update --- */ + +#if FM_BUSY_FLAG_SUPPORT +#define TIME_TYPE attotime +#define UNDEFINED_TIME attotime_zero +#define FM_GET_TIME_NOW(machine) timer_get_time(machine) +#define ADD_TIMES(t1, t2) attotime_add((t1), (t2)) +#define COMPARE_TIMES(t1, t2) attotime_compare((t1), (t2)) +#define MULTIPLY_TIME_BY_INT(t,i) attotime_mul(t, i) +#endif + +/* compiler dependence */ +#if 0 +#ifndef OSD_CPU_H +#define OSD_CPU_H +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +#endif /* OSD_CPU_H */ +#endif + +typedef stream_sample_t FMSAMPLE; +/* +#if (FM_SAMPLE_BITS==16) +typedef INT16 FMSAMPLE; +#endif +#if (FM_SAMPLE_BITS==8) +typedef unsigned char FMSAMPLE; +#endif +*/ + +typedef void (*FM_TIMERHANDLER)(void *param,int c,int cnt,int clock); +typedef void (*FM_IRQHANDLER)(void *param,int irq); +/* FM_TIMERHANDLER : Stop or Start timer */ +/* int n = chip number */ +/* int c = Channel 0=TimerA,1=TimerB */ +/* int count = timer count (0=stop) */ +/* doube stepTime = step time of one count (sec.)*/ + +/* FM_IRQHHANDLER : IRQ level changing sense */ +/* int n = chip number */ +/* int irq = IRQ level 0=OFF,1=ON */ + +/** + * @brief Initialize chip and return the instance + * @param param Unused, keep NULL + * @param baseclock YM2612 clock + * @param rate Output sample rate + * @param TimerHandler Keep NULL + * @param IRQHandler Keep NULL + * @return Chip instance or NULL on any error + */ +static void * ym2612_init(void *param, int baseclock, int rate, + FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); +/** + * @brief Free chip instance + * @param chip Chip instance + */ +static void ym2612_shutdown(void *chip); +/** + * @brief Reset state of the chip + * @param chip Chip instance + */ +static void ym2612_reset_chip(void *chip); +/** + * @brief Generate stereo output of specified length + * @param chip Chip instance + * @param buffer Output sound buffer + * @param frames Output buffer size in frames (one frame - two array entries of the buffer) + * @param mix 0 - override buffer data, 1 - mix output data with a content of the buffer + */ +static void ym2612_generate(void *chip, FMSAMPLE *buffer, int frames, int mix); +#define ym2612_update_one(chip, buffer, length) ym2612_generate(chip, buffer, length, 0) + +/** + * @brief Single-Sample generation prepare + * @param chip Chip instance + */ +static void ym2612_pre_generate(void *chip); +/** + * @brief Generate single stereo PCM frame. Will be used native sample rate of 53267 Hz + * @param chip Chip instance + * @param buffer One stereo PCM frame + */ +static void ym2612_generate_one_native(void *chip, FMSAMPLE buffer[2]); + +/* void ym2612_post_generate(void *chip, int length); */ + +static int ym2612_write(void *chip, int a,unsigned char v); +#if 0 +static unsigned char ym2612_read(void *chip,int a); +static int ym2612_timer_over(void *chip, int c ); +#endif + +#ifdef __STATE_H__ +static void ym2612_postload(void *chip); +#endif + +static void ym2612_set_mutemask(void *chip, UINT32 MuteMask); +#if 0 +static void ym2612_setoptions(UINT8 Flags); +#endif + + +static stream_sample_t *DUMMYBUF = NULL; + +/* shared function building option */ +#define BUILD_OPN (BUILD_YM2203||BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B||BUILD_YM2612||BUILD_YM3438) +#define BUILD_OPN_PRESCALER (BUILD_YM2203||BUILD_YM2608) + +#define RSM_ENABLE 0 +#define RSM_FRAC 10 + +/* globals */ +#define TYPE_SSG 0x01 /* SSG support */ +#define TYPE_LFOPAN 0x02 /* OPN type LFO and PAN */ +#define TYPE_6CH 0x04 /* FM 6CH / 3CH */ +#define TYPE_DAC 0x08 /* YM2612's DAC device */ +#define TYPE_ADPCM 0x10 /* two ADPCM units */ +#define TYPE_2610 0x20 /* bogus flag to differentiate 2608 from 2610 */ + + +#define TYPE_YM2203 (TYPE_SSG) +#define TYPE_YM2608 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM) +#define TYPE_YM2610 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM |TYPE_2610) +#define TYPE_YM2612 (TYPE_DAC |TYPE_LFOPAN |TYPE_6CH) + + +/* globals */ +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (envelope generator timing) */ +#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ +#define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ + +#define FREQ_MASK ((1<>3) + +/* sin waveform table in 'decibel' scale */ +static unsigned int sin_tab[SIN_LEN]; + +/* sustain level table (3dB per step) */ +/* bit0, bit1, bit2, bit3, bit4, bit5, bit6 */ +/* 1, 2, 4, 8, 16, 32, 64 (value)*/ +/* 0.75, 1.5, 3, 6, 12, 24, 48 (dB)*/ + +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +/* attenuation value (10 bits) = (SL << 2) << 3 */ +#define SC(db) (UINT32) ( db * (4.0/ENV_STEP) ) +static const UINT32 sl_table[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) +}; +#undef SC + + +#define RATE_STEPS (8) +static const UINT8 eg_inc[19*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..11 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..11 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..11 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..11 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 12 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 12 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 12 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 12 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 13 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 13 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 13 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 13 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rate 14 0 (increment by 4) */ +/*13 */ 4,4, 4,8, 4,4, 4,8, /* rate 14 1 */ +/*14 */ 4,8, 4,8, 4,8, 4,8, /* rate 14 2 */ +/*15 */ 4,8, 8,8, 4,8, 8,8, /* rate 14 3 */ + +/*16 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 8) */ +/*17 */ 16,16,16,16,16,16,16,16, /* rates 15 2, 15 3 for attack */ +/*18 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(17) in this table - it's directly in the code */ +static const UINT8 eg_rate_select2612[32+64+32]={ /* Envelope Generator rates (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates (same as Rate 0) */ +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), + +/* rates 00-11 */ +/* +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +*/ +O(18),O(18),O( 2),O( 3), /* from Nemesis's tests on real YM2612 hardware */ +O( 0),O( 1),O( 2),O( 2), /* Nemesis's tests */ + +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 12 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 13 */ +O( 8),O( 9),O(10),O(11), + +/* rate 14 */ +O(12),O(13),O(14),O(15), + +/* rate 15 */ +O(16),O(16),O(16),O(16), + +/* 32 dummy rates (same as 15 3) */ +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) + +}; +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15*/ +/*shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 */ +/*mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 */ + +#define O(a) (a*1) +static const UINT8 eg_rate_shift[32+64+32]={ /* Envelope Generator counter shifts (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates */ +/* O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), */ + +/* fixed (should be the same as rate 0, even if it makes no difference since increment value is 0 for these rates) */ +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), + +/* rates 00-11 */ +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), +O( 0),O( 0),O( 0),O( 0), + +/* rate 12 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 32 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) + +}; +#undef O + +static const UINT8 dt_tab[4 * 32]={ +/* this is YM2151 and YM2612 phase increment data (in 10.10 fixed point format)*/ +/* FD=0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* FD=1 */ + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, +/* FD=2 */ + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, +/* FD=3 */ + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 +}; + + +/* OPN key frequency number -> key code follow table */ +/* fnum higher 4bit -> keycode lower 2bit */ +static const UINT8 opn_fktable[16] = {0,0,0,0,0,0,0,1,2,3,3,3,3,3,3,3}; + + +/* 8 LFO speed parameters */ +/* each value represents number of samples that one LFO level will last for */ +static const UINT32 lfo_samples_per_step[8] = {108, 77, 71, 67, 62, 44, 8, 5}; + + + +/*There are 4 different LFO AM depths available, they are: + 0 dB, 1.4 dB, 5.9 dB, 11.8 dB + Here is how it is generated (in EG steps): + + 11.8 dB = 0, 2, 4, 6, 8, 10,12,14,16...126,126,124,122,120,118,....4,2,0 + 5.9 dB = 0, 1, 2, 3, 4, 5, 6, 7, 8....63, 63, 62, 61, 60, 59,.....2,1,0 + 1.4 dB = 0, 0, 0, 0, 1, 1, 1, 1, 2,...15, 15, 15, 15, 14, 14,.....0,0,0 + + (1.4 dB is losing precision as you can see) + + It's implemented as generator from 0..126 with step 2 then a shift + right N times, where N is: + 8 for 0 dB + 3 for 1.4 dB + 1 for 5.9 dB + 0 for 11.8 dB +*/ +static const UINT8 lfo_ams_depth_shift[4] = {8, 3, 1, 0}; + + + +/*There are 8 different LFO PM depths available, they are: + 0, 3.4, 6.7, 10, 14, 20, 40, 80 (cents) + + Modulation level at each depth depends on F-NUMBER bits: 4,5,6,7,8,9,10 + (bits 8,9,10 = FNUM MSB from OCT/FNUM register) + + Here we store only first quarter (positive one) of full waveform. + Full table (lfo_pm_table) containing all 128 waveforms is build + at run (init) time. + + One value in table below represents 4 (four) basic LFO steps + (1 PM step = 4 AM steps). + + For example: + at LFO SPEED=0 (which is 108 samples per basic LFO step) + one value from "lfo_pm_output" table lasts for 432 consecutive + samples (4*108=432) and one full LFO waveform cycle lasts for 13824 + samples (32*432=13824; 32 because we store only a quarter of whole + waveform in the table below) +*/ +static const UINT8 lfo_pm_output[7*8][8]={ /* 7 bits meaningful (of F-NUMBER), 8 LFO output levels per one depth (out of 32), 8 LFO depths */ +/* FNUM BIT 4: 000 0001xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 6 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 7 */ {0, 0, 0, 0, 1, 1, 1, 1}, + +/* FNUM BIT 5: 000 0010xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 6 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 7 */ {0, 0, 1, 1, 2, 2, 2, 3}, + +/* FNUM BIT 6: 000 0100xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 1}, +/* DEPTH 5 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 6 */ {0, 0, 1, 1, 2, 2, 2, 3}, +/* DEPTH 7 */ {0, 0, 2, 3, 4, 4, 5, 6}, + +/* FNUM BIT 7: 000 1000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 1, 1}, +/* DEPTH 3 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 4 */ {0, 0, 0, 1, 1, 1, 1, 2}, +/* DEPTH 5 */ {0, 0, 1, 1, 2, 2, 2, 3}, +/* DEPTH 6 */ {0, 0, 2, 3, 4, 4, 5, 6}, +/* DEPTH 7 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, + +/* FNUM BIT 8: 001 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 2 */ {0, 0, 0, 1, 1, 1, 2, 2}, +/* DEPTH 3 */ {0, 0, 1, 1, 2, 2, 3, 3}, +/* DEPTH 4 */ {0, 0, 1, 2, 2, 2, 3, 4}, +/* DEPTH 5 */ {0, 0, 2, 3, 4, 4, 5, 6}, +/* DEPTH 6 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, +/* DEPTH 7 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, + +/* FNUM BIT 9: 010 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 2, 2, 2, 2}, +/* DEPTH 2 */ {0, 0, 0, 2, 2, 2, 4, 4}, +/* DEPTH 3 */ {0, 0, 2, 2, 4, 4, 6, 6}, +/* DEPTH 4 */ {0, 0, 2, 4, 4, 4, 6, 8}, +/* DEPTH 5 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, +/* DEPTH 6 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, +/* DEPTH 7 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, + +/* FNUM BIT10: 100 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 4, 4, 4, 4}, +/* DEPTH 2 */ {0, 0, 0, 4, 4, 4, 8, 8}, +/* DEPTH 3 */ {0, 0, 4, 4, 8, 8, 0xc, 0xc}, +/* DEPTH 4 */ {0, 0, 4, 8, 8, 8, 0xc,0x10}, +/* DEPTH 5 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, +/* DEPTH 6 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, +/* DEPTH 7 */ {0, 0,0x20,0x30,0x40,0x40,0x50,0x60}, + +}; + +/* all 128 LFO PM waveforms */ +static INT32 lfo_pm_table[128*8*32]; /* 128 combinations of 7 bits meaningful (of F-NUMBER), 8 LFO depths, 32 LFO output levels per one depth */ + +/* register number to channel number , slot offset */ +#define OPN_CHAN(N) (N&3) +#define OPN_SLOT(N) ((N>>2)&3) + +/* slot number */ +#define SLOT1 0 +#define SLOT2 2 +#define SLOT3 1 +#define SLOT4 3 + +/* bit0 = Right enable , bit1 = Left enable */ +#define OUTD_RIGHT 1 +#define OUTD_LEFT 2 +#define OUTD_CENTER 3 + + +/* save output as raw 16-bit sample */ +/* #define SAVE_SAMPLE */ + +#ifdef SAVE_SAMPLE +static FILE *sample[1]; + #if 1 /*save to MONO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #else /*save to STEREO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + pom = rt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #endif +#endif + + +/* struct describing a single operator (SLOT) */ +typedef struct +{ + INT32 *DT; /* detune :dt_tab[DT] */ + UINT8 KSR; /* key scale rate :3-KSR */ + UINT32 ar; /* attack rate */ + UINT32 d1r; /* decay rate */ + UINT32 d2r; /* sustain rate */ + UINT32 rr; /* release rate */ + UINT8 ksr; /* key scale rate :kcode>>(3-KSR) */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + + /* Phase Generator */ + UINT32 phase; /* phase counter */ + INT32 Incr; /* phase step */ + + /* Envelope Generator */ + UINT8 state; /* phase type */ + UINT32 tl; /* total level: TL << 3 */ + INT32 volume; /* envelope counter */ + UINT32 sl; /* sustain level:sl_table[SL] */ + UINT32 vol_out; /* current output from EG circuit (without AM from LFO) */ + + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT8 eg_sh_d1r; /* (decay state) */ + UINT8 eg_sel_d1r; /* (decay state) */ + UINT8 eg_sh_d2r; /* (sustain state) */ + UINT8 eg_sel_d2r; /* (sustain state) */ + UINT8 eg_sh_rr; /* (release state) */ + UINT8 eg_sel_rr; /* (release state) */ + + UINT8 ssg; /* SSG-EG waveform */ + UINT8 ssgn; /* SSG-EG negated output */ + + UINT8 key; /* 0=last key was KEY OFF, 1=KEY ON */ + + /* LFO */ + UINT32 AMmask; /* AM enable flag */ + +} FM_SLOT; + +typedef struct +{ + FM_SLOT SLOT[4]; /* four SLOTs (operators) */ + + UINT8 ALGO; /* algorithm */ + UINT8 FB; /* feedback shift */ + INT32 op1_out[2]; /* op1 output for feedback */ + + INT32 *connect1; /* SLOT1 output pointer */ + INT32 *connect3; /* SLOT3 output pointer */ + INT32 *connect2; /* SLOT2 output pointer */ + INT32 *connect4; /* SLOT4 output pointer */ + + INT32 *mem_connect;/* where to put the delayed sample (MEM) */ + INT32 mem_value; /* delayed sample (MEM) value */ + + INT32 pms; /* channel PMS */ + UINT8 ams; /* channel AMS */ + + UINT32 fc; /* fnum,blk:adjusted to sample rate */ + UINT8 kcode; /* key code: */ + UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ + UINT8 Muted; +} FM_CH; + + +typedef struct +{ + /* running_device *device; */ + void * param; /* this chip parameter */ + double freqbase; /* frequency base */ + int timer_prescaler; /* timer prescaler */ + UINT8 irq; /* interrupt level */ + UINT8 irqmask; /* irq mask */ +#if FM_BUSY_FLAG_SUPPORT + TIME_TYPE busy_expiry_time; /* expiry time of the busy status */ +#endif + UINT32 clock; /* master clock (Hz) */ + UINT32 rate; /* internal sampling rate (Hz) */ +#if RSM_ENABLE + INT32 rateratio; /* resampling ratio */ + INT32 framecnt; /* resampling frames count*/ + FMSAMPLE cur_sample[2]; /* previous sample */ + FMSAMPLE prev_sample[2]; /* previous sample */ +#endif + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT32 mode; /* mode CSM / 3SLOT */ + UINT8 fn_h; /* freq latch */ + UINT8 prescaler_sel; /* prescaler selector */ + INT32 TA; /* timer a */ + INT32 TAC; /* timer a counter */ + UINT8 TB; /* timer b */ + INT32 TBC; /* timer b counter */ + /* local time tables */ + INT32 dt_tab[8][32]; /* DeTune table */ + /* Extention Timer and IRQ handler */ + FM_TIMERHANDLER timer_handler; + FM_IRQHANDLER IRQ_Handler; + const ssg_callbacks *SSG; +} FM_ST; + + + +/***********************************************************/ +/* OPN unit */ +/***********************************************************/ + +/* OPN 3slot struct */ +typedef struct +{ + UINT32 fc[3]; /* fnum3,blk3: calculated */ + UINT8 fn_h; /* freq3 latch */ + UINT8 kcode[3]; /* key code */ + UINT32 block_fnum[3]; /* current fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ + UINT8 key_csm; /* CSM mode Key-ON flag */ +} FM_3SLOT; + +/* OPN/A/B common state */ +typedef struct +{ + UINT8 type; /* chip type */ + FM_ST ST; /* general state */ + FM_3SLOT SL3; /* 3 slot mode state */ + FM_CH *P_CH; /* pointer of CH */ + unsigned int pan[6*2]; /* fm channels output masks (0xffffffff = enable) */ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/144/3 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow;/* envelope generator timer overlfows every 3 samples (on real chip) */ + + + /* there are 2048 FNUMs that can be generated using FNUM/BLK registers + but LFO works with one more bit of a precision so we really need 4096 elements */ + UINT32 fn_table[4096]; /* fnumber->increment counter */ + UINT32 fn_max; /* maximal phase increment (used for phase overflow) */ + + /* LFO */ + UINT8 lfo_cnt; /* current LFO phase (out of 128) */ + UINT32 lfo_timer; /* current LFO phase runs at LFO frequency */ + UINT32 lfo_timer_add; /* step of lfo_timer */ + UINT32 lfo_timer_overflow; /* LFO timer overflows every N samples (depends on LFO frequency) */ + UINT32 LFO_AM; /* current LFO AM step */ + UINT32 LFO_PM; /* current LFO PM step */ + + INT32 m2,c1,c2; /* Phase Modulation input for operators 2,3,4 */ + INT32 mem; /* one sample delay memory */ + INT32 out_fm[6]; /* outputs of working channels */ + +} FM_OPN; + +/* here's the virtual YM2612 */ +typedef struct +{ + UINT8 REGS[512]; /* registers */ + FM_OPN OPN; /* OPN state */ + FM_CH CH[6]; /* channel state */ + UINT8 addr_A1; /* address line A1 */ + + /* dac output (YM2612) */ + /* int dacen; */ + UINT8 dacen; + UINT8 dac_test; + INT32 dacout; + UINT8 MuteDAC; + + UINT8 WaveOutMode; + INT32 WaveL; + INT32 WaveR; +} YM2612; + +/* log output level */ +#define LOG_ERR 3 /* ERROR */ +#define LOG_WAR 2 /* WARNING */ +#define LOG_INF 1 /* INFORMATION */ +#define LOG_LEVEL LOG_INF + +#ifndef __RAINE__ +#define LOG(n,x) do { if( (n)>=LOG_LEVEL ) logerror x; } while (0) +#endif + +/* limitter */ +#define Limit(val, max,min) { \ + if ( val > max ) val = max; \ + else if ( val < min ) val = min; \ +} + +#if 0 +#define USE_VGM_INIT_SWITCH +static UINT8 IsVGMInit = 0; +#endif +static UINT8 PseudoSt = 0x00; +/*#include +static FILE* hFile; +static UINT32 FileSample;*/ + +/* status set and IRQ handling */ +INLINE void FM_STATUS_SET(FM_ST *ST,int flag) +{ + /* set status flag */ + ST->status |= flag; + if ( !(ST->irq) && (ST->status & ST->irqmask) ) + { + ST->irq = 1; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param,1); + } +} + +/* status reset and IRQ handling */ +INLINE void FM_STATUS_RESET(FM_ST *ST,int flag) +{ + /* reset status flag */ + ST->status &=~flag; + if ( (ST->irq) && !(ST->status & ST->irqmask) ) + { + ST->irq = 0; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param,0); + } +} + +/* IRQ mask set */ +INLINE void FM_IRQMASK_SET(FM_ST *ST,int flag) +{ + ST->irqmask = flag; + /* IRQ handling check */ + FM_STATUS_SET(ST,0); + FM_STATUS_RESET(ST,0); +} + +INLINE void FM_KEYON(FM_OPN *OPN, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + + /* Note by Valley Bell: + I assume that the CSM mode shouldn't affect channels + other than FM3, so I added a check for it here.*/ + if( !SLOT->key && (!OPN->SL3.key_csm || CH == &OPN->P_CH[3])) + { + /* restart Phase Generator */ + SLOT->phase = 0; + + /* reset SSG-EG inversion flag */ + SLOT->ssgn = 0; + + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; + } + else + { + /* force attenuation level to 0 */ + SLOT->volume = MIN_ATT_INDEX; + + /* directly switch to Decay (or Sustain) */ + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; + } + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + SLOT->key = 1; +} + +INLINE void FM_KEYOFF(FM_OPN *OPN, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + + if (SLOT->key && (!OPN->SL3.key_csm || CH == &OPN->P_CH[3])) + { +#ifdef USE_VGM_INIT_SWITCH + if (IsVGMInit) /* workaround for VGMs trimmed with VGMTool */ + { + SLOT->state = EG_OFF; + SLOT->volume = MAX_ATT_INDEX; + SLOT->vol_out= MAX_ATT_INDEX; + } + else +#endif + if (SLOT->state>EG_REL) + { + SLOT->state = EG_REL; /* phase -> Release */ + + /* SSG-EG specific update */ + if (SLOT->ssg&0x08) + { + /* convert EG attenuation level */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) + SLOT->volume = (0x200 - SLOT->volume); + + /* force EG attenuation level */ + if (SLOT->volume >= 0x200) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + } + + SLOT->key = 0; +} + +INLINE void FM_KEYON_CSM(FM_OPN *OPN, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + + if( !SLOT->key && !OPN->SL3.key_csm) + { + /* restart Phase Generator */ + SLOT->phase = 0; + + /* reset SSG-EG inversion flag */ + SLOT->ssgn = 0; + + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; + } + else + { + /* force attenuation level to 0 */ + SLOT->volume = MIN_ATT_INDEX; + + /* directly switch to Decay (or Sustain) */ + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; + } + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } +} + +INLINE void FM_KEYOFF_CSM(FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + if (!SLOT->key) + { +#ifdef USE_VGM_INIT_SWITCH + if (IsVGMInit) + { + SLOT->state = EG_OFF; + SLOT->volume = MAX_ATT_INDEX; + SLOT->vol_out= MAX_ATT_INDEX; + } + else +#endif + if (SLOT->state>EG_REL) + { + SLOT->state = EG_REL; /* phase -> Release */ + + /* SSG-EG specific update */ + if (SLOT->ssg&0x08) + { + /* convert EG attenuation level */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) + SLOT->volume = (0x200 - SLOT->volume); + + /* force EG attenuation level */ + if (SLOT->volume >= 0x200) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + } +} + +/* OPN Mode Register Write */ +INLINE void set_timers( FM_OPN *OPN, FM_ST *ST, void *n, int v ) +{ + /* b7 = CSM MODE */ + /* b6 = 3 slot mode */ + /* b5 = reset b */ + /* b4 = reset a */ + /* b3 = timer enable b */ + /* b2 = timer enable a */ + /* b1 = load b */ + /* b0 = load a */ + + if ((OPN->ST.mode ^ v) & 0xC0) + { + /* phase increment need to be recalculated */ + OPN->P_CH[2].SLOT[SLOT1].Incr=-1; + + /* CSM mode disabled and CSM key ON active*/ + if (((v & 0xC0) != 0x80) && OPN->SL3.key_csm) + { + /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT1); + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT2); + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT3); + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT4); + OPN->SL3.key_csm = 0; + } + } + + /* reset Timer b flag */ + if( v & 0x20 ) + FM_STATUS_RESET(ST,0x02); + /* reset Timer a flag */ + if( v & 0x10 ) + FM_STATUS_RESET(ST,0x01); + /* load b */ + if ((v&2) && !(ST->mode&2)) + { + ST->TBC = ( 256-ST->TB)<<4; + /* External timer handler */ + if (ST->timer_handler) (ST->timer_handler)(n,1,ST->TBC * ST->timer_prescaler,(int)ST->clock); + } + /* load a */ + if ((v&1) && !(ST->mode&1)) + { + ST->TAC = (1024-ST->TA); + /* External timer handler */ + if (ST->timer_handler) (ST->timer_handler)(n,0,ST->TAC * ST->timer_prescaler,(int)ST->clock); + ST->TAC *= 4096; + } + + ST->mode = (UINT32)v; +} + + +/* Timer A Overflow */ +INLINE void TimerAOver(FM_ST *ST) +{ + /* set status (if enabled) */ + if(ST->mode & 0x04) FM_STATUS_SET(ST,0x01); + /* clear or reload the counter */ + ST->TAC = (1024-ST->TA); + if (ST->timer_handler) (ST->timer_handler)(ST->param,0,ST->TAC * ST->timer_prescaler,(int)ST->clock); + ST->TAC *= 4096; +} +/* Timer B Overflow */ +INLINE void TimerBOver(FM_ST *ST) +{ + /* set status (if enabled) */ + if(ST->mode & 0x08) FM_STATUS_SET(ST,0x02); + /* clear or reload the counter */ + ST->TBC = ( 256-ST->TB)<<4; + if (ST->timer_handler) (ST->timer_handler)(ST->param,1,ST->TBC * ST->timer_prescaler,(int)ST->clock); +} + + +#if FM_INTERNAL_TIMER +/* ----- internal timer mode , update timer */ +/* Valley Bell: defines fixed */ + +/* ---------- calculate timer A ---------- */ + #define INTERNAL_TIMER_A(ST,CSM_CH) \ + { \ + if( (ST)->TAC && ((ST)->timer_handler==0) ) \ + if( ((ST)->TAC -= (int)((ST)->freqbase*4096)) <= 0 ) \ + { \ + TimerAOver( ST ); \ + /* CSM mode total level latch and auto key on */ \ + if( (ST)->mode & 0x80 ) \ + CSMKeyControll( OPN, CSM_CH ); \ + } \ + } +/* ---------- calculate timer B ---------- */ + #define INTERNAL_TIMER_B(ST,step) \ + { \ + if( (ST)->TBC && ((ST)->timer_handler==0) ) \ + if( ((ST)->TBC -= (int)((ST)->freqbase*4096*step)) <= 0 ) \ + TimerBOver( ST ); \ + } +#else /* FM_INTERNAL_TIMER */ +/* external timer mode */ +#define INTERNAL_TIMER_A(ST,CSM_CH) +#define INTERNAL_TIMER_B(ST,step) +#endif /* FM_INTERNAL_TIMER */ + + + +#if FM_BUSY_FLAG_SUPPORT +#define FM_BUSY_CLEAR(ST) ((ST)->busy_expiry_time = UNDEFINED_TIME) +INLINE UINT8 FM_STATUS_FLAG(FM_ST *ST) +{ + if( COMPARE_TIMES(ST->busy_expiry_time, UNDEFINED_TIME) != 0 ) + { + if (COMPARE_TIMES(ST->busy_expiry_time, FM_GET_TIME_NOW(ST->device->machine)) > 0) + return ST->status | 0x80; /* with busy */ + /* expire */ + FM_BUSY_CLEAR(ST); + } + return ST->status; +} +INLINE void FM_BUSY_SET(FM_ST *ST,int busyclock ) +{ + TIME_TYPE expiry_period = MULTIPLY_TIME_BY_INT(ATTOTIME_IN_HZ(ST->clock), busyclock * ST->timer_prescaler); + ST->busy_expiry_time = ADD_TIMES(FM_GET_TIME_NOW(ST->device->machine), expiry_period); +} +#else +#define FM_STATUS_FLAG(ST) ((ST)->status) +#define FM_BUSY_SET(ST,bclock) {} +#define FM_BUSY_CLEAR(ST) {} +#endif + + +/* set algorithm connection */ +INLINE void setup_connection( FM_OPN *OPN, FM_CH *CH, int ch ) +{ + INT32 *carrier = &OPN->out_fm[ch]; + + INT32 **om1 = &CH->connect1; + INT32 **om2 = &CH->connect3; + INT32 **oc1 = &CH->connect2; + + INT32 **memc = &CH->mem_connect; + + switch( CH->ALGO ) + { + case 0: + /* M1---C1---MEM---M2---C2---OUT */ + *om1 = &OPN->c1; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 1: + /* M1------+-MEM---M2---C2---OUT */ + /* C1-+ */ + *om1 = &OPN->mem; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 2: + /* M1-----------------+-C2---OUT */ + /* C1---MEM---M2-+ */ + *om1 = &OPN->c2; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 3: + /* M1---C1---MEM------+-C2---OUT */ + /* M2-+ */ + *om1 = &OPN->c1; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->c2; + break; + case 4: + /* M1---C1-+-OUT */ + /* M2---C2-+ */ + /* MEM: not used */ + *om1 = &OPN->c1; + *oc1 = carrier; + *om2 = &OPN->c2; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + case 5: + /* +----C1----+ */ + /* M1-+-MEM---M2-+-OUT */ + /* +----C2----+ */ + *om1 = 0; /* special mark */ + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->m2; + break; + case 6: + /* M1---C1-+ */ + /* M2-+-OUT */ + /* C2-+ */ + /* MEM: not used */ + *om1 = &OPN->c1; + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + case 7: + /* M1-+ */ + /* C1-+-OUT */ + /* M2-+ */ + /* C2-+ */ + /* MEM: not used*/ + *om1 = carrier; + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + } + + CH->connect4 = carrier; +} + +/* set detune & multiple */ +INLINE void set_det_mul(FM_ST *ST,FM_CH *CH,FM_SLOT *SLOT,int v) +{ + SLOT->mul = (v&0x0f)? (v&0x0f)*2 : 1; + SLOT->DT = ST->dt_tab[(v>>4)&7]; + CH->SLOT[SLOT1].Incr=-1; +} + +/* set total level */ +INLINE void set_tl(FM_CH *CH,FM_SLOT *SLOT , int v) +{ + SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */ + (void)CH; + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04)) && (SLOT->state > EG_REL)) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; +} + +/* set attack rate & key scale */ +INLINE void set_ar_ksr(UINT8 type, FM_CH *CH,FM_SLOT *SLOT,int v) +{ + UINT8 old_KSR = SLOT->KSR; + (void)type; + + SLOT->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->KSR = 3-(v>>6); + if (SLOT->KSR != old_KSR) + { + CH->SLOT[SLOT1].Incr=-1; + } + + /* Even if it seems unnecessary, in some odd case, KSR and KC are both modified */ + /* and could result in SLOT->kc remaining unchanged. */ + /* In such case, AR values would not be recalculated despite SLOT->ar has changed */ + /* This fixes the introduction music of Batman & Robin (Eke-Eke) */ + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select2612[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 18*RATE_STEPS; /* verified by Nemesis on real hardware */ + } +} + +/* set decay rate */ +INLINE void set_dr(UINT8 type, FM_SLOT *SLOT,int v) +{ + (void)type; + SLOT->d1r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; + SLOT->eg_sel_d1r= eg_rate_select2612[SLOT->d1r + SLOT->ksr]; +} + +/* set sustain rate */ +INLINE void set_sr(UINT8 type, FM_SLOT *SLOT,int v) +{ + (void)type; + SLOT->d2r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; + SLOT->eg_sel_d2r= eg_rate_select2612[SLOT->d2r + SLOT->ksr]; +} + +/* set release rate */ +INLINE void set_sl_rr(UINT8 type, FM_SLOT *SLOT,int v) +{ + (void)type; + SLOT->sl = sl_table[ v>>4 ]; + + /* check EG state changes */ + if ((SLOT->state == EG_DEC) && (SLOT->volume >= (INT32)(SLOT->sl))) + SLOT->state = EG_SUS; + + SLOT->rr = 34 + ((v&0x0f)<<2); + + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; + SLOT->eg_sel_rr = eg_rate_select2612[SLOT->rr + SLOT->ksr]; +} + +/* advance LFO to next sample */ +INLINE void advance_lfo(FM_OPN *OPN) +{ + if (OPN->lfo_timer_overflow) /* LFO enabled ? */ + { + /* increment LFO timer */ + OPN->lfo_timer += OPN->lfo_timer_add; + + /* when LFO is enabled, one level will last for 108, 77, 71, 67, 62, 44, 8 or 5 samples */ + while (OPN->lfo_timer >= OPN->lfo_timer_overflow) + { + OPN->lfo_timer -= OPN->lfo_timer_overflow; + + /* There are 128 LFO steps */ + OPN->lfo_cnt = ( OPN->lfo_cnt + 1 ) & 127; + + /* Valley Bell: Replaced old code (non-inverted triangle) with + the one from Genesis Plus GX 1.71. */ + /* triangle (inverted) */ + /* AM: from 126 to 0 step -2, 0 to 126 step +2 */ + if (OPN->lfo_cnt<64) + OPN->LFO_AM = (UINT32)(OPN->lfo_cnt ^ 63) << 1; + else + OPN->LFO_AM = (UINT32)(OPN->lfo_cnt & 63) << 1; + + /* PM works with 4 times slower clock */ + OPN->LFO_PM = OPN->lfo_cnt >> 2; + } + } +} + +INLINE void advance_eg_channel(FM_OPN *OPN, FM_SLOT *SLOT) +{ + /* unsigned int out; */ + unsigned int i = 4; /* four operators per channel */ + + do + { + switch(SLOT->state) + { + case EG_ATT: /* attack phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_ar)-1))) + { + /* update attenuation level */ + SLOT->volume += (~SLOT->volume * (eg_inc[SLOT->eg_sel_ar + ((OPN->eg_cnt>>SLOT->eg_sh_ar)&7)]))>>4; + + /* check phase transition*/ + if (SLOT->volume <= MIN_ATT_INDEX) + { + SLOT->volume = MIN_ATT_INDEX; + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; /* special case where SL=0 */ + } + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) /* SSG-EG Output Inversion */ + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + break; + + case EG_DEC: /* decay phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_d1r)-1))) + { + /* SSG EG type */ + if (SLOT->ssg&0x08) + { + /* update attenuation level */ + if (SLOT->volume < 0x200) + { + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; + + /* recalculate EG output */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) /* SSG-EG Output Inversion */ + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + } + else + { + /* update attenuation level */ + SLOT->volume += eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + /* check phase transition*/ + if (SLOT->volume >= (INT32)(SLOT->sl)) + SLOT->state = EG_SUS; + } + break; + + case EG_SUS: /* sustain phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_d2r)-1))) + { + /* SSG EG type */ + if (SLOT->ssg&0x08) + { + /* update attenuation level */ + if (SLOT->volume < 0x200) + { + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; + + /* recalculate EG output */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) /* SSG-EG Output Inversion */ + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + else + { + /* update attenuation level */ + SLOT->volume += eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; + + /* check phase transition*/ + if ( SLOT->volume >= MAX_ATT_INDEX ) + SLOT->volume = MAX_ATT_INDEX; + /* do not change SLOT->state (verified on real chip) */ + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + break; + + case EG_REL: /* release phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_rr)-1))) + { + /* SSG EG type */ + if (SLOT->ssg&0x08) + { + /* update attenuation level */ + if (SLOT->volume < 0x200) + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; + /* check phase transition */ + if (SLOT->volume >= 0x200) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + } + else + { + /* update attenuation level */ + SLOT->volume += eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; + + /* check phase transition*/ + if (SLOT->volume >= MAX_ATT_INDEX) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + } + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + + } + break; + } + + /* Valley Bell: These few lines are missing in Genesis Plus GX' ym2612 core file. + Disabling them fixes the SSG-EG. + Additional Note: Asterix and the Great Rescue: Level 1 sounds "better" with these lines, + but less accurate. */ + #if 0 + out = ((UINT32)SLOT->volume); + + /* negate output (changes come from alternate bit, init comes from attack bit) */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn&2) && (SLOT->state > EG_REL)) + out ^= MAX_ATT_INDEX; + + /* we need to store the result here because we are going to change ssgn + in next instruction */ + SLOT->vol_out = out + SLOT->tl; + #endif + + SLOT++; + i--; + } while (i); + +} + +/* SSG-EG update process */ +/* The behavior is based upon Nemesis tests on real hardware */ +/* This is actually executed before each samples */ +INLINE void update_ssg_eg_channel(FM_SLOT *SLOT) +{ + unsigned int i = 4; /* four operators per channel */ + + do + { + /* detect SSG-EG transition */ + /* this is not required during release phase as the attenuation has been forced to MAX and output invert flag is not used */ + /* if an Attack Phase is programmed, inversion can occur on each sample */ + if ((SLOT->ssg & 0x08) && (SLOT->volume >= 0x200) && (SLOT->state > EG_REL)) + { + if (SLOT->ssg & 0x01) /* bit 0 = hold SSG-EG */ + { + /* set inversion flag */ + if (SLOT->ssg & 0x02) + SLOT->ssgn = 4; + + /* force attenuation level during decay phases */ + if ((SLOT->state != EG_ATT) && !(SLOT->ssgn ^ (SLOT->ssg & 0x04))) + SLOT->volume = MAX_ATT_INDEX; + } + else /* loop SSG-EG */ + { + /* toggle output inversion flag or reset Phase Generator */ + if (SLOT->ssg & 0x02) + SLOT->ssgn ^= 4; + else + SLOT->phase = 0; + + /* same as Key ON */ + if (SLOT->state != EG_ATT) + { + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; + } + else + { + /* Attack Rate is maximal: directly switch to Decay or Substain */ + SLOT->volume = MIN_ATT_INDEX; + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; + } + } + } + + /* recalculate EG output */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + /* next slot */ + SLOT++; + i--; + } while (i); +} + + +INLINE void update_phase_lfo_slot(FM_OPN *OPN, FM_SLOT *SLOT, INT32 pms, UINT32 block_fnum) +{ + UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; + INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + pms + OPN->LFO_PM ]; + + block_fnum = block_fnum*2 + lfo_fn_table_index_offset; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + UINT8 blk = (block_fnum&0x7000) >> 12; + UINT32 fn = block_fnum & 0xfff; + + /* recalculate keyscale code */ + /*int kc = (blk<<2) | opn_fktable[fn >> 7];*/ + /* This really stupid bug caused a read outside of the + array [size 0x10] and returned invalid values. + This caused an annoying vibrato for some notes. + (Note: seems to be a copy-and-paste from OPNWriteReg -> case 0xA0) + Why are MAME cores always SOO buggy ?! */ + /* Oh, and before I forget: it's correct in fm.c */ + int kc = (blk<<2) | opn_fktable[fn >> 8]; + /* Thanks to Blargg - his patch that helped me to find this bug */ + + /* recalculate (frequency) phase increment counter */ + int fc = (OPN->fn_table[fn]>>(7-blk)) + SLOT->DT[kc]; + + /* (frequency) phase overflow (credits to Nemesis) */ + if (fc < 0) fc += OPN->fn_max; + + /* update phase */ + SLOT->phase += (fc * SLOT->mul) >> 1; + } + else /* LFO phase modulation = zero */ + { + SLOT->phase += SLOT->Incr; + } +} + +INLINE void update_phase_lfo_channel(FM_OPN *OPN, FM_CH *CH) +{ + UINT32 block_fnum = CH->block_fnum; + + UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; + INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + CH->pms + OPN->LFO_PM ]; + + block_fnum = block_fnum*2 + lfo_fn_table_index_offset; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + UINT8 blk = (block_fnum&0x7000) >> 12; + UINT32 fn = block_fnum & 0xfff; + + /* recalculate keyscale code */ + /*int kc = (blk<<2) | opn_fktable[fn >> 7];*/ + /* the same stupid bug as above */ + int kc = (blk<<2) | opn_fktable[fn >> 8]; + + /* recalculate (frequency) phase increment counter */ + int fc = (OPN->fn_table[fn]>>(7-blk)); + + /* (frequency) phase overflow (credits to Nemesis) */ + int finc = fc + CH->SLOT[SLOT1].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT1].phase += (finc*CH->SLOT[SLOT1].mul) >> 1; + + finc = fc + CH->SLOT[SLOT2].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT2].phase += (finc*CH->SLOT[SLOT2].mul) >> 1; + + finc = fc + CH->SLOT[SLOT3].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT3].phase += (finc*CH->SLOT[SLOT3].mul) >> 1; + + finc = fc + CH->SLOT[SLOT4].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT4].phase += (finc*CH->SLOT[SLOT4].mul) >> 1; + } + else /* LFO phase modulation = zero */ + { + CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; + CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; + CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; + CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; + } +} + +/* update phase increment and envelope generator */ +INLINE void refresh_fc_eg_slot(FM_OPN *OPN, FM_SLOT *SLOT , int fc , int kc ) +{ + int ksr = kc >> SLOT->KSR; + + fc += SLOT->DT[kc]; + + /* detects frequency overflow (credits to Nemesis) */ + if (fc < 0) fc += OPN->fn_max; + + /* (frequency) phase increment counter */ + SLOT->Incr = (fc * SLOT->mul) >> 1; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + + /* calculate envelope generator rates */ + if ((SLOT->ar + SLOT->ksr) < 32+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select2612[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 18*RATE_STEPS; /* verified by Nemesis on real hardware (Attack phase is blocked) */ + } + + SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; + SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; + + SLOT->eg_sel_d1r= eg_rate_select2612[SLOT->d1r + SLOT->ksr]; + SLOT->eg_sel_d2r= eg_rate_select2612[SLOT->d2r + SLOT->ksr]; + SLOT->eg_sel_rr = eg_rate_select2612[SLOT->rr + SLOT->ksr]; + } +} + +/* update phase increment counters */ +INLINE void refresh_fc_eg_chan(FM_OPN *OPN, FM_CH *CH ) +{ + if( CH->SLOT[SLOT1].Incr==-1) + { + int fc = CH->fc; + int kc = CH->kcode; + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT1] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT2] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT3] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT4] , fc , kc ); + } +} + +#define volume_calc(OP) ((OP)->vol_out + (AM & (OP)->AMmask)) + +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm) +{ + UINT32 p; + + p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + (pm<<15))) >> FREQ_SH ) & SIN_MASK ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm) +{ + UINT32 p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK ]; + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE void chan_calc(YM2612 *F2612, FM_OPN *OPN, FM_CH *CH) +{ + UINT32 AM = OPN->LFO_AM >> CH->ams; + unsigned int eg_out; + + if (CH->Muted) + return; + + OPN->m2 = OPN->c1 = OPN->c2 = OPN->mem = 0; + + *CH->mem_connect = CH->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ + + eg_out = volume_calc(&CH->SLOT[SLOT1]); + { + INT32 out = CH->op1_out[0] + CH->op1_out[1]; + CH->op1_out[0] = CH->op1_out[1]; + + if( !CH->connect1 ) + { + /* algorithm 5 */ + OPN->mem = OPN->c1 = OPN->c2 = CH->op1_out[0]; + } + else + { + /* other algorithms */ + *CH->connect1 += CH->op1_out[0]; + } + + + CH->op1_out[1] = 0; + if( eg_out < ENV_QUIET ) /* SLOT 1 */ + { + if (!CH->FB) + out=0; + + CH->op1_out[1] = op_calc1(CH->SLOT[SLOT1].phase, eg_out, (out<FB) ); + } + } + + eg_out = volume_calc(&CH->SLOT[SLOT3]); + if( eg_out < ENV_QUIET ) /* SLOT 3 */ + *CH->connect3 += op_calc(CH->SLOT[SLOT3].phase, eg_out, OPN->m2); + + eg_out = volume_calc(&CH->SLOT[SLOT2]); + if( eg_out < ENV_QUIET ) /* SLOT 2 */ + *CH->connect2 += op_calc(CH->SLOT[SLOT2].phase, eg_out, OPN->c1); + + eg_out = volume_calc(&CH->SLOT[SLOT4]); + if( eg_out < ENV_QUIET ) /* SLOT 4 */ + *CH->connect4 += op_calc(CH->SLOT[SLOT4].phase, eg_out, OPN->c2); + + + /* store current MEM */ + CH->mem_value = OPN->mem; + + /* update phase counters AFTER output calculations */ + if(CH->pms) + { + /* add support for 3 slot mode */ + if ((OPN->ST.mode & 0xC0) && (CH == &F2612->CH[2])) + { + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT1], CH->pms, OPN->SL3.block_fnum[1]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT2], CH->pms, OPN->SL3.block_fnum[2]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT3], CH->pms, OPN->SL3.block_fnum[0]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT4], CH->pms, CH->block_fnum); + } + else update_phase_lfo_channel(OPN, CH); + } + else /* no LFO phase modulation */ + { + CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; + CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; + CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; + CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; + } +} + +static void FMCloseTable( void ) +{ +#ifdef SAVE_SAMPLE + fclose(sample[0]); +#endif + return; +} + + +/* CSM Key Controll */ +INLINE void CSMKeyControll(FM_OPN *OPN, FM_CH *CH) +{ + /* all key ON (verified by Nemesis on real hardware) */ + FM_KEYON_CSM(OPN,CH,SLOT1); + FM_KEYON_CSM(OPN,CH,SLOT2); + FM_KEYON_CSM(OPN,CH,SLOT3); + FM_KEYON_CSM(OPN,CH,SLOT4); + OPN->SL3.key_csm = 1; +} + +#ifdef __STATE_H__ +/* FM channel save , internal state only */ +static void FMsave_state_channel(running_device *device,FM_CH *CH,int num_ch) +{ + int slot , ch; + + for(ch=0;chop1_out); + state_save_register_device_item(device, ch, CH->fc); + /* slots */ + for(slot=0;slot<4;slot++) + { + FM_SLOT *SLOT = &CH->SLOT[slot]; + state_save_register_device_item(device, ch * 4 + slot, SLOT->phase); + state_save_register_device_item(device, ch * 4 + slot, SLOT->state); + state_save_register_device_item(device, ch * 4 + slot, SLOT->volume); + } + } +} + +static void FMsave_state_st(running_device *device,FM_ST *ST) +{ +#if FM_BUSY_FLAG_SUPPORT + state_save_register_device_item(device, 0, ST->busy_expiry_time.seconds ); + state_save_register_device_item(device, 0, ST->busy_expiry_time.attoseconds ); +#endif + state_save_register_device_item(device, 0, ST->address ); + state_save_register_device_item(device, 0, ST->irq ); + state_save_register_device_item(device, 0, ST->irqmask ); + state_save_register_device_item(device, 0, ST->status ); + state_save_register_device_item(device, 0, ST->mode ); + state_save_register_device_item(device, 0, ST->prescaler_sel ); + state_save_register_device_item(device, 0, ST->fn_h ); + state_save_register_device_item(device, 0, ST->TA ); + state_save_register_device_item(device, 0, ST->TAC ); + state_save_register_device_item(device, 0, ST->TB ); + state_save_register_device_item(device, 0, ST->TBC ); +} +#endif /* _STATE_H */ + +#if BUILD_OPN +/* write a OPN mode register 0x20-0x2f */ +static void OPNWriteMode(FM_OPN *OPN, int r, int v) +{ + UINT8 c; + FM_CH *CH; + + switch(r) + { + case 0x21: /* Test */ + break; + case 0x22: /* LFO FREQ (YM2608/YM2610/YM2610B/YM2612) */ + if (v&8) /* LFO enabled ? */ + { + #if 0 + if (!OPN->lfo_timer_overflow) + { + /* restart LFO */ + OPN->lfo_cnt = 0; + OPN->lfo_timer = 0; + OPN->LFO_AM = 0; + OPN->LFO_PM = 0; + } + #endif + + OPN->lfo_timer_overflow = lfo_samples_per_step[v&7] << LFO_SH; + } + else + { + /* Valley Bell: Ported from Genesis Plus GX 1.71 + hold LFO waveform in reset state */ + OPN->lfo_timer_overflow = 0; + OPN->lfo_timer = 0; + OPN->lfo_cnt = 0; + + + OPN->LFO_PM = 0; + OPN->LFO_AM = 126; + /* OPN->lfo_timer_overflow = 0; */ + } + break; + case 0x24: /* timer A High 8*/ + OPN->ST.TA = (OPN->ST.TA & 0x03)|(((int)v)<<2); + break; + case 0x25: /* timer A Low 2*/ + OPN->ST.TA = (OPN->ST.TA & 0x3fc)|(v&3); + break; + case 0x26: /* timer B */ + OPN->ST.TB = (UINT8)v; + break; + case 0x27: /* mode, timer control */ + set_timers( OPN, &(OPN->ST),OPN->ST.param,v ); + break; + case 0x28: /* key on / off */ + c = v & 0x03; + if( c == 3 ) break; + if( (v&0x04) && (OPN->type & TYPE_6CH) ) c+=3; + CH = OPN->P_CH; + CH = &CH[c]; + if(v&0x10) FM_KEYON(OPN,CH,SLOT1); else FM_KEYOFF(OPN,CH,SLOT1); + if(v&0x20) FM_KEYON(OPN,CH,SLOT2); else FM_KEYOFF(OPN,CH,SLOT2); + if(v&0x40) FM_KEYON(OPN,CH,SLOT3); else FM_KEYOFF(OPN,CH,SLOT3); + if(v&0x80) FM_KEYON(OPN,CH,SLOT4); else FM_KEYOFF(OPN,CH,SLOT4); + break; + } +} + +/* write a OPN register (0x30-0xff) */ +static void OPNWriteReg(FM_OPN *OPN, int r, int v) +{ + FM_CH *CH; + FM_SLOT *SLOT; + + UINT8 c = OPN_CHAN(r); + + if (c == 3) return; /* 0xX3,0xX7,0xXB,0xXF */ + + if (r >= 0x100) c+=3; + + CH = OPN->P_CH; + CH = &CH[c]; + + SLOT = &(CH->SLOT[OPN_SLOT(r)]); + + switch( r & 0xf0 ) { + case 0x30: /* DET , MUL */ + set_det_mul(&OPN->ST,CH,SLOT,v); + break; + + case 0x40: /* TL */ + set_tl(CH,SLOT,v); + break; + + case 0x50: /* KS, AR */ + set_ar_ksr(OPN->type,CH,SLOT,v); + break; + + case 0x60: /* bit7 = AM ENABLE, DR */ + set_dr(OPN->type, SLOT,v); + + if(OPN->type & TYPE_LFOPAN) /* YM2608/2610/2610B/2612 */ + { + SLOT->AMmask = (v&0x80) ? ~0 : 0; + } + break; + + case 0x70: /* SR */ + set_sr(OPN->type,SLOT,v); + break; + + case 0x80: /* SL, RR */ + set_sl_rr(OPN->type,SLOT,v); + break; + + case 0x90: /* SSG-EG */ + SLOT->ssg = v&0x0f; + + /* recalculate EG output */ + if (SLOT->state > EG_REL) + { + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + /* SSG-EG envelope shapes : + + E AtAlH + 1 0 0 0 \\\\ + + 1 0 0 1 \___ + + 1 0 1 0 \/\/ + ___ + 1 0 1 1 \ + + 1 1 0 0 //// + ___ + 1 1 0 1 / + + 1 1 1 0 /\/\ + + 1 1 1 1 /___ + + + E = SSG-EG enable + + + The shapes are generated using Attack, Decay and Sustain phases. + + Each single character in the diagrams above represents this whole + sequence: + + - when KEY-ON = 1, normal Attack phase is generated (*without* any + difference when compared to normal mode), + + - later, when envelope level reaches minimum level (max volume), + the EG switches to Decay phase (which works with bigger steps + when compared to normal mode - see below), + + - later when envelope level passes the SL level, + the EG swithes to Sustain phase (which works with bigger steps + when compared to normal mode - see below), + + - finally when envelope level reaches maximum level (min volume), + the EG switches to Attack phase again (depends on actual waveform). + + Important is that when switch to Attack phase occurs, the phase counter + of that operator will be zeroed-out (as in normal KEY-ON) but not always. + (I havent found the rule for that - perhaps only when the output level is low) + + The difference (when compared to normal Envelope Generator mode) is + that the resolution in Decay and Sustain phases is 4 times lower; + this results in only 256 steps instead of normal 1024. + In other words: + when SSG-EG is disabled, the step inside of the EG is one, + when SSG-EG is enabled, the step is four (in Decay and Sustain phases). + + Times between the level changes are the same in both modes. + + + Important: + Decay 1 Level (so called SL) is compared to actual SSG-EG output, so + it is the same in both SSG and no-SSG modes, with this exception: + + when the SSG-EG is enabled and is generating raising levels + (when the EG output is inverted) the SL will be found at wrong level !!! + For example, when SL=02: + 0 -6 = -6dB in non-inverted EG output + 96-6 = -90dB in inverted EG output + Which means that EG compares its level to SL as usual, and that the + output is simply inverted afterall. + + + The Yamaha's manuals say that AR should be set to 0x1f (max speed). + That is not necessary, but then EG will be generating Attack phase. + + */ + + + break; + + case 0xa0: + switch( OPN_SLOT(r) ) + { + case 0: /* 0xa0-0xa2 : FNUM1 */ +#ifdef USE_VGM_INIT_SWITCH + if (IsVGMInit) + OPN->ST.fn_h = CH->block_fnum >> 8; +#endif + { + UINT32 fn = (((UINT32)( (OPN->ST.fn_h)&7))<<8) + v; + UINT8 blk = OPN->ST.fn_h>>3; + /* keyscale code */ + CH->kcode = (blk<<2) | opn_fktable[fn >> 7]; + /* phase increment counter */ + CH->fc = OPN->fn_table[fn*2]>>(7-blk); + + /* store fnum in clear form for LFO PM calculations */ + CH->block_fnum = (blk<<11) | fn; + + CH->SLOT[SLOT1].Incr=-1; + } + break; + case 1: /* 0xa4-0xa6 : FNUM2,BLK */ + OPN->ST.fn_h = v&0x3f; +#ifdef USE_VGM_INIT_SWITCH + if (IsVGMInit) // workaround for stupid Kega Fusion init block + CH->block_fnum = (OPN->ST.fn_h << 8) | (CH->block_fnum & 0xFF); +#endif + break; + case 2: /* 0xa8-0xaa : 3CH FNUM1 */ +#ifdef USE_VGM_INIT_SWITCH + if (IsVGMInit) + OPN->SL3.fn_h = OPN->SL3.block_fnum[c] >> 8; +#endif + if(r < 0x100) + { + UINT32 fn = (((UINT32)(OPN->SL3.fn_h&7))<<8) + v; + UINT8 blk = OPN->SL3.fn_h>>3; + /* keyscale code */ + OPN->SL3.kcode[c]= (blk<<2) | opn_fktable[fn >> 7]; + /* phase increment counter */ + OPN->SL3.fc[c] = OPN->fn_table[fn*2]>>(7-blk); + OPN->SL3.block_fnum[c] = (blk<<11) | fn; + (OPN->P_CH)[2].SLOT[SLOT1].Incr=-1; + } + break; + case 3: /* 0xac-0xae : 3CH FNUM2,BLK */ + if(r < 0x100) + { + OPN->SL3.fn_h = v&0x3f; +#ifdef USE_VGM_INIT_SWITCH + if (IsVGMInit) + OPN->SL3.block_fnum[c] = (OPN->SL3.fn_h << 8) | (OPN->SL3.block_fnum[c] & 0xFF); +#endif + } + break; + } + break; + + case 0xb0: + switch( OPN_SLOT(r) ) + { + case 0: /* 0xb0-0xb2 : FB,ALGO */ + { + unsigned char feedback = ((v>>3)&7); + CH->ALGO = v&7; + CH->FB = feedback ? feedback + 6 : 0; + setup_connection( OPN, CH, c ); + } + break; + case 1: /* 0xb4-0xb6 : L , R , AMS , PMS (YM2612/YM2610B/YM2610/YM2608) */ + if( OPN->type & TYPE_LFOPAN) + { + /* b0-2 PMS */ + CH->pms = (v & 7) * 32; /* CH->pms = PM depth * 32 (index in lfo_pm_table) */ + + /* b4-5 AMS */ + CH->ams = lfo_ams_depth_shift[(v>>4) & 0x03]; + + /* PAN : b7 = L, b6 = R */ + OPN->pan[ c*2 ] = (v & 0x80) ? ~0 : 0; + OPN->pan[ c*2+1 ] = (v & 0x40) ? ~0 : 0; + + } + break; + } + break; + } +} + +/* initialize time tables */ +static void init_timetables(FM_OPN *OPN, double freqbase) +{ + int i,d; + double rate; + + /* DeTune table */ + for (d = 0;d <= 3;d++) + { + for (i = 0;i <= 31;i++) + { + rate = ((double)dt_tab[d*32 + i]) * freqbase * (1<<(FREQ_SH-10)); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ + OPN->ST.dt_tab[d][i] = (INT32) rate; + OPN->ST.dt_tab[d+4][i] = -OPN->ST.dt_tab[d][i]; + } + } + + /* there are 2048 FNUMs that can be generated using FNUM/BLK registers + but LFO works with one more bit of a precision so we really need 4096 elements */ + /* calculate fnumber -> increment counter table */ + for(i = 0; i < 4096; i++) + { + /* freq table for octave 7 */ + /* OPN phase increment counter = 20bit */ + /* the correct formula is : F-Number = (144 * fnote * 2^20 / M) / 2^(B-1) */ + /* where sample clock is M/144 */ + /* this means the increment value for one clock sample is FNUM * 2^(B-1) = FNUM * 64 for octave 7 */ + /* we also need to handle the ratio between the chip frequency and the emulated frequency (can be 1.0) */ + OPN->fn_table[i] = (UINT32)( (double)i * 32 * freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ + } + + /* maximal frequency is required for Phase overflow calculation, register size is 17 bits (Nemesis) */ + OPN->fn_max = (UINT32)( (double)0x20000 * freqbase * (1<<(FREQ_SH-10)) ); +} + +/* prescaler set (and make time tables) */ +static void OPNSetPres(FM_OPN *OPN, int pres, int timer_prescaler, int SSGpres) +{ + /* frequency base */ + OPN->ST.freqbase = (OPN->ST.rate) ? ((double)OPN->ST.clock / OPN->ST.rate) / pres : 0; + + /* EG is updated every 3 samples */ + OPN->eg_timer_add = (UINT32)((1<ST.freqbase); + OPN->eg_timer_overflow = ( 3 ) * (1<lfo_timer_add = (UINT32)((1<ST.freqbase); + + /* Timer base time */ + OPN->ST.timer_prescaler = timer_prescaler; + + /* SSG part prescaler set */ + if( SSGpres ) (*OPN->ST.SSG->set_clock)( OPN->ST.param, OPN->ST.clock * 2 / SSGpres ); + + /* make time tables */ + init_timetables(OPN, OPN->ST.freqbase); +} + +static void reset_channels( FM_ST *ST , FM_CH *CH , int num ) +{ + int c,s; + (void)ST; + + for( c = 0 ; c < num ; c++ ) + { + /* memset(&CH[c], 0x00, sizeof(FM_CH)); */ + CH[c].mem_value = 0; + CH[c].op1_out[0] = 0; + CH[c].op1_out[1] = 0; + CH[c].fc = 0; + for(s = 0 ; s < 4 ; s++ ) + { + /* memset(&CH[c].SLOT[s], 0x00, sizeof(FM_SLOT)); */ + CH[c].SLOT[s].Incr = -1; + CH[c].SLOT[s].key = 0; + CH[c].SLOT[s].phase = 0; + CH[c].SLOT[s].ssg = 0; + CH[c].SLOT[s].ssgn = 0; + CH[c].SLOT[s].state= EG_OFF; + CH[c].SLOT[s].volume = MAX_ATT_INDEX; + CH[c].SLOT[s].vol_out= MAX_ATT_INDEX; + } + } +} + +/* initialize generic tables */ +static void init_tables(void) +{ + signed int i,x; + signed int n; + double o,m; + + /* build Linear Power Table */ + for (x=0; x>= 4; /* 12 bits here */ + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + n <<= 2; /* 13 bits here (as in real chip) */ + + + /* 14 bits (with sign bit) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + /* one entry in the 'Power' table use the following format, xxxxxyyyyyyyys with: */ + /* s = sign bit */ + /* yyyyyyyy = 8-bits decimal part (0-TL_RES_LEN) */ + /* xxxxx = 5-bits integer 'shift' value (0-31) but, since Power table output is 13 bits, */ + /* any value above 13 (included) would be discarded. */ + for (i=1; i<13; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + } + + /* build Logarithmic Sinus table */ + for (i=0; i0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + + /* 13-bits (8.5) value is formatted for above 'Power' table */ + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + } + + /* build LFO PM modulation table */ + for(i = 0; i < 8; i++) /* 8 PM depths */ + { + UINT8 fnum; + for (fnum=0; fnum<128; fnum++) /* 7 bits meaningful of F-NUMBER */ + { + UINT8 value; + UINT8 step; + UINT32 offset_depth = i; + UINT32 offset_fnum_bit; + UINT32 bit_tmp; + + for (step=0; step<8; step++) + { + value = 0; + for (bit_tmp=0; bit_tmp<7; bit_tmp++) /* 7 bits */ + { + if (fnum & (1<CH; + FMSAMPLE *bufOut = buffer; + int i; +#if !RSM_ENABLE + FMSAMPLE bufTmp[2]; +#endif + + ym2612_pre_generate(chip); + + if (!frames) + { + update_ssg_eg_channel(&cch[0].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[1].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[2].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[3].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[4].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[5].SLOT[SLOT1]); + } + + /* buffering */ + for(i=0 ; i < frames ; i++) + { +#if RSM_ENABLE + while(F2612->OPN.ST.framecnt >= F2612->OPN.ST.rateratio)/* Copy-Pasta from Nuked */ + { + /* Copy-Pasta from Nuked */ + F2612->OPN.ST.prev_sample[0] = F2612->OPN.ST.cur_sample[0]; + F2612->OPN.ST.prev_sample[1] = F2612->OPN.ST.cur_sample[1]; + ym2612_generate_one_native(chip, F2612->OPN.ST.cur_sample); + F2612->OPN.ST.framecnt -= F2612->OPN.ST.rateratio; + /* Copy-Pasta from Nuked */ + } + if (mix) + { + *bufOut++ += (FMSAMPLE)((F2612->OPN.ST.prev_sample[0] * (F2612->OPN.ST.rateratio - F2612->OPN.ST.framecnt) + + F2612->OPN.ST.cur_sample[0] * F2612->OPN.ST.framecnt) / F2612->OPN.ST.rateratio); + *bufOut++ += (FMSAMPLE)((F2612->OPN.ST.prev_sample[1] * (F2612->OPN.ST.rateratio - F2612->OPN.ST.framecnt) + + F2612->OPN.ST.cur_sample[1] * F2612->OPN.ST.framecnt) / F2612->OPN.ST.rateratio); + } else { + *bufOut++ = (FMSAMPLE)((F2612->OPN.ST.prev_sample[0] * (F2612->OPN.ST.rateratio - F2612->OPN.ST.framecnt) + + F2612->OPN.ST.cur_sample[0] * F2612->OPN.ST.framecnt) / F2612->OPN.ST.rateratio); + *bufOut++ = (FMSAMPLE)((F2612->OPN.ST.prev_sample[1] * (F2612->OPN.ST.rateratio - F2612->OPN.ST.framecnt) + + F2612->OPN.ST.cur_sample[1] * F2612->OPN.ST.framecnt) / F2612->OPN.ST.rateratio); + } + F2612->OPN.ST.framecnt += 1 << RSM_FRAC; +#else + if (mix) + { + ym2612_generate_one_native(chip, bufTmp); + bufOut[0] += bufTmp[0]; + bufOut[1] += bufTmp[1]; + } + else + { + ym2612_generate_one_native(chip, bufOut); + } + bufOut += 2; +#endif + } + /* ym2612_post_generate(chip, frames); */ +} + +void ym2612_pre_generate(void *chip) +{ + YM2612 *F2612 = (YM2612 *)chip; + FM_OPN *OPN = &F2612->OPN; + FM_CH *cch = F2612->CH; + + /* refresh PG and EG */ + refresh_fc_eg_chan( OPN, &cch[0] ); + refresh_fc_eg_chan( OPN, &cch[1] ); + if( (OPN->ST.mode & 0xc0) ) + { + /* 3SLOT MODE */ + if( cch[2].SLOT[SLOT1].Incr==-1) + { + refresh_fc_eg_slot(OPN, &cch[2].SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); + refresh_fc_eg_slot(OPN, &cch[2].SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); + refresh_fc_eg_slot(OPN, &cch[2].SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); + refresh_fc_eg_slot(OPN, &cch[2].SLOT[SLOT4] , cch[2].fc , cch[2].kcode ); + } + } else + refresh_fc_eg_chan( OPN, &cch[2] ); + refresh_fc_eg_chan( OPN, &cch[3] ); + refresh_fc_eg_chan( OPN, &cch[4] ); + refresh_fc_eg_chan( OPN, &cch[5] ); +} + +void ym2612_generate_one_native(void *chip, FMSAMPLE buffer[]) +{ + YM2612 *F2612 = (YM2612 *)chip; + FM_OPN *OPN = &F2612->OPN; + INT32 *out_fm = OPN->out_fm; + FM_CH *cch = F2612->CH; + INT32 dacout; + int lt,rt; + + if (! F2612->MuteDAC) + dacout = F2612->dacout; + else + dacout = 0; + + /* clear outputs */ + out_fm[0] = 0; + out_fm[1] = 0; + out_fm[2] = 0; + out_fm[3] = 0; + out_fm[4] = 0; + out_fm[5] = 0; + + /* update SSG-EG output */ + update_ssg_eg_channel(&cch[0].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[1].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[2].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[3].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[4].SLOT[SLOT1]); + update_ssg_eg_channel(&cch[5].SLOT[SLOT1]); + + /* calculate FM */ + if (! F2612->dac_test) + { + chan_calc(F2612, OPN, &cch[0]); + chan_calc(F2612, OPN, &cch[1]); + chan_calc(F2612, OPN, &cch[2]); + chan_calc(F2612, OPN, &cch[3]); + chan_calc(F2612, OPN, &cch[4]); + if( F2612->dacen ) + cch[5].connect4 += dacout; + else + chan_calc(F2612, OPN, &cch[5]); + } + else + { + out_fm[0] = out_fm[1] = dacout; + out_fm[2] = out_fm[3] = dacout; + out_fm[5] = dacout; + } + + /* advance LFO */ + advance_lfo(OPN); + + /* advance envelope generator */ + OPN->eg_timer += OPN->eg_timer_add; + while (OPN->eg_timer >= OPN->eg_timer_overflow) + { + /* reset EG timer */ + OPN->eg_timer -= OPN->eg_timer_overflow; + /* increment EG counter */ + OPN->eg_cnt++; + /* EG counter is 12-bit only and zero value is skipped (verified on real hardware) */ + if (OPN->eg_cnt == 4096) + OPN->eg_cnt = 1; + + /* advance envelope generator */ + advance_eg_channel(OPN, &cch[0].SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[1].SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[2].SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[3].SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[4].SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[5].SLOT[SLOT1]); + } + + /*fprintf(hFile, "%u", FileSample, out_fm[0]); + for (lt = 0; lt < 6; lt ++) + fprintf(hFile, "\t%d", out_fm[lt]); + fprintf(hFile, "\n"); + FileSample ++;*/ + + if (out_fm[0] > 8192) out_fm[0] = 8192; + else if (out_fm[0] < -8192) out_fm[0] = -8192; + if (out_fm[1] > 8192) out_fm[1] = 8192; + else if (out_fm[1] < -8192) out_fm[1] = -8192; + if (out_fm[2] > 8192) out_fm[2] = 8192; + else if (out_fm[2] < -8192) out_fm[2] = -8192; + if (out_fm[3] > 8192) out_fm[3] = 8192; + else if (out_fm[3] < -8192) out_fm[3] = -8192; + if (out_fm[4] > 8192) out_fm[4] = 8192; + else if (out_fm[4] < -8192) out_fm[4] = -8192; + if (out_fm[5] > 8192) out_fm[5] = 8192; + else if (out_fm[5] < -8192) out_fm[5] = -8192; + + /* 6-channels mixing */ + lt = ((out_fm[0]>>0) & OPN->pan[0]); + rt = ((out_fm[0]>>0) & OPN->pan[1]); + lt += ((out_fm[1]>>0) & OPN->pan[2]); + rt += ((out_fm[1]>>0) & OPN->pan[3]); + lt += ((out_fm[2]>>0) & OPN->pan[4]); + rt += ((out_fm[2]>>0) & OPN->pan[5]); + lt += ((out_fm[3]>>0) & OPN->pan[6]); + rt += ((out_fm[3]>>0) & OPN->pan[7]); + if (! F2612->dac_test) + { + lt += ((out_fm[4]>>0) & OPN->pan[8]); + rt += ((out_fm[4]>>0) & OPN->pan[9]); + } + else + { + lt += dacout; + lt += dacout; + } + lt += ((out_fm[5]>>0) & OPN->pan[10]); + rt += ((out_fm[5]>>0) & OPN->pan[11]); + + /* Limit( lt, MAXOUT, MINOUT ); */ + /* Limit( rt, MAXOUT, MINOUT ); */ + + #ifdef SAVE_SAMPLE + SAVE_ALL_CHANNELS + #endif + + /* buffering */ + if (F2612->WaveOutMode & 0x01) + F2612->WaveL = lt; + if (F2612->WaveOutMode & 0x02) + F2612->WaveR = rt; + if (F2612->WaveOutMode ^ 0x03) + F2612->WaveOutMode ^= 0x03; + + buffer[0] = (FMSAMPLE)(F2612->WaveL / 2); + buffer[1] = (FMSAMPLE)(F2612->WaveR / 2); + + /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ + /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ + OPN->SL3.key_csm <<= 1; + + /* timer A control */ + /* INTERNAL_TIMER_A( &OPN->ST , cch[2] ) */ + { + if( OPN->ST.TAC && (OPN->ST.timer_handler==0) ) + if( (OPN->ST.TAC -= (int)(OPN->ST.freqbase*4096)) <= 0 ) + { + TimerAOver( &OPN->ST ); + /* CSM mode total level latch and auto key on */ + if( OPN->ST.mode & 0x80 ) + CSMKeyControll( OPN, &cch[2] ); + } + } + + /* CSM Mode Key ON still disabled */ + if (OPN->SL3.key_csm & 2) + { + /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ + FM_KEYOFF_CSM(&cch[2],SLOT1); + FM_KEYOFF_CSM(&cch[2],SLOT2); + FM_KEYOFF_CSM(&cch[2],SLOT3); + FM_KEYOFF_CSM(&cch[2],SLOT4); + OPN->SL3.key_csm = 0; + } +} + +#if 0 +void ym2612_post_generate(void *chip, int length) +{ + YM2612 *F2612 = (YM2612 *)chip; + /* timer B control */ + INTERNAL_TIMER_B(&F2612->OPN.ST, length); +} +#endif + +#ifdef __STATE_H__ +void ym2612_postload(void *chip) +{ + if (chip) + { + YM2612 *F2612 = (YM2612 *)chip; + int r; + + /* DAC data & port */ + F2612->dacout = ((int)F2612->REGS[0x2a] - 0x80) << 6; /* level unknown */ + F2612->dacen = F2612->REGS[0x2d] & 0x80; + /* OPN registers */ + /* DT / MULTI , TL , KS / AR , AMON / DR , SR , SL / RR , SSG-EG */ + for(r=0x30;r<0x9e;r++) + if((r&3) != 3) + { + OPNWriteReg(&F2612->OPN,r,F2612->REGS[r]); + OPNWriteReg(&F2612->OPN,r|0x100,F2612->REGS[r|0x100]); + } + /* FB / CONNECT , L / R / AMS / PMS */ + for(r=0xb0;r<0xb6;r++) + if((r&3) != 3) + { + OPNWriteReg(&F2612->OPN,r,F2612->REGS[r]); + OPNWriteReg(&F2612->OPN,r|0x100,F2612->REGS[r|0x100]); + } + /* channels */ + /*FM_channel_postload(F2612->CH,6);*/ + } +} + +static void YM2612_save_state(YM2612 *F2612, running_device *device) +{ + state_save_register_device_item_array(device, 0, F2612->REGS); + FMsave_state_st(device,&F2612->OPN.ST); + FMsave_state_channel(device,F2612->CH,6); + /* 3slots */ + state_save_register_device_item_array(device, 0, F2612->OPN.SL3.fc); + state_save_register_device_item(device, 0, F2612->OPN.SL3.fn_h); + state_save_register_device_item_array(device, 0, F2612->OPN.SL3.kcode); + /* address register1 */ + state_save_register_device_item(device, 0, F2612->addr_A1); +} +#endif /* _STATE_H */ + +/* initialize YM2612 emulator(s) */ +static void * ym2612_init(void *param, int clock, int rate, + FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler) +{ + YM2612 *F2612; + + if (clock <= 0 || rate <= 0) + return NULL; /* Forbid zero clock and sample rate */ + + /* allocate extend state space */ + /* F2612 = auto_alloc_clear(device->machine, YM2612); */ + F2612 = (YM2612 *)malloc(sizeof(YM2612)); + if (F2612 == NULL) + return NULL; + memset(F2612, 0x00, sizeof(YM2612)); + /* allocate total level table (128kb space) */ + init_tables(); + + F2612->OPN.ST.param = param; + F2612->OPN.type = TYPE_YM2612; + F2612->OPN.P_CH = F2612->CH; + /* F2612->OPN.ST.device = device; */ + F2612->OPN.ST.clock = clock; +#if RSM_ENABLE + F2612->OPN.ST.rate = 53267; + F2612->OPN.ST.rateratio = (INT32)(UINT32)((((UINT64)144 * rate) << RSM_FRAC) / clock); + F2612->OPN.ST.framecnt = 1 << RSM_FRAC; + memset(&(F2612->OPN.ST.cur_sample), 0x00, sizeof(FMSAMPLE) * 2); + memset(&(F2612->OPN.ST.prev_sample), 0x00, sizeof(FMSAMPLE) * 2); +#else + F2612->OPN.ST.rate = rate; +#endif + /* F2612->OPN.ST.irq = 0; */ + /* F2612->OPN.ST.status = 0; */ + /* Extend handler */ + F2612->OPN.ST.timer_handler = timer_handler; + F2612->OPN.ST.IRQ_Handler = IRQHandler; + + if (PseudoSt) + F2612->WaveOutMode = 0x01; + else + F2612->WaveOutMode = 0x03; + /*hFile = fopen("YM2612.log", "wt"); + fprintf(hFile, "Clock: %d, Sample Rate: %d\n", clock, rate); + fprintf(hFile, "Sample\tCh 0\tCh 1\tCh 2\tCh 3\tCh 4\tCh 5\n"); + FileSample = 0;*/ + +#ifdef __STATE_H__ + YM2612_save_state(F2612, device); +#endif + return F2612; +} + +/* shut down emulator */ +static void ym2612_shutdown(void *chip) +{ + YM2612 *F2612 = (YM2612 *)chip; + /* fclose(hFile); */ + + FMCloseTable(); + /* auto_free(F2612->OPN.ST.device->machine, F2612); */ + free(F2612); +} + +/* reset one of chip */ +static void ym2612_reset_chip(void *chip) +{ + int i; + YM2612 *F2612 = (YM2612 *)chip; + FM_OPN *OPN = &F2612->OPN; + + OPNSetPres( OPN, 6*24, 6*24, 0); + /* status clear */ + FM_IRQMASK_SET(&OPN->ST,0x03); + FM_BUSY_CLEAR(&OPN->ST); + /* OPNWriteMode(OPN,0x27,0x30); */ /* mode 0 , timer reset */ + +#if RSM_ENABLE + /* Resampler's state */ + F2612->OPN.ST.framecnt = 1 << RSM_FRAC; + memset(&(F2612->OPN.ST.cur_sample), 0x00, sizeof(FMSAMPLE) * 2); + memset(&(F2612->OPN.ST.prev_sample), 0x00, sizeof(FMSAMPLE) * 2); +#endif + + OPN->eg_timer = 0; + OPN->eg_cnt = 0; + + OPN->lfo_timer = 0; + OPN->lfo_cnt = 0; + OPN->LFO_AM = 126; + OPN->LFO_PM = 0; + + OPN->ST.TAC = 0; + OPN->ST.TBC = 0; + + OPN->SL3.key_csm = 0; + + OPN->ST.status = 0; + OPN->ST.mode = 0; + + memset(F2612->REGS, 0x00, sizeof(UINT8) * 512); + + OPNWriteMode(OPN,0x22,0x00); + + OPNWriteMode(OPN,0x27,0x30); + OPNWriteMode(OPN,0x26,0x00); + OPNWriteMode(OPN,0x25,0x00); + OPNWriteMode(OPN,0x24,0x00); + + reset_channels( &OPN->ST , &F2612->CH[0] , 6 ); + + for(i = 0xb6 ; i >= 0xb4 ; i-- ) + { + OPNWriteReg(OPN,i ,0xc0); + OPNWriteReg(OPN,i|0x100,0xc0); + } + for(i = 0xb2 ; i >= 0x30 ; i-- ) + { + OPNWriteReg(OPN,i ,0); + OPNWriteReg(OPN,i|0x100,0); + } + + /* DAC mode clear */ + F2612->dacen = 0; + F2612->dac_test = 0; + F2612->dacout = 0; + + if (F2612->WaveOutMode == 0x02) + F2612->WaveOutMode >>= 1; +} + +/* YM2612 write */ +/* n = number */ +/* a = address */ +/* v = value */ +static int ym2612_write(void *chip, int a, UINT8 v) +{ + YM2612 *F2612 = (YM2612 *)chip; + int addr; + + v &= 0xff; /* adjust to 8 bit bus */ + + switch( a&3) + { + case 0: /* address port 0 */ + F2612->OPN.ST.address = v; + F2612->addr_A1 = 0; + break; + + case 1: /* data port 0 */ + if (F2612->addr_A1 != 0) + break; /* verified on real YM2608 */ + + addr = F2612->OPN.ST.address; + F2612->REGS[addr] = v; + switch( addr & 0xf0 ) + { + case 0x20: /* 0x20-0x2f Mode */ + switch( addr ) + { + case 0x2a: /* DAC data (YM2612) */ + ym2612_update_one(chip, DUMMYBUF, 0); + F2612->dacout = ((int)v - 0x80) << 6; /* level unknown */ + break; + case 0x2b: /* DAC Sel (YM2612) */ + /* b7 = dac enable */ + F2612->dacen = v & 0x80; + break; + case 0x2C: /* undocumented: DAC Test Reg */ + /* b5 = volume enable */ + F2612->dac_test = v & 0x20; + break; + default: /* OPN section */ + /* ym2612_update_req(F2612->OPN.ST.param); */ + ym2612_update_one(chip, DUMMYBUF, 0); + /* write register */ + OPNWriteMode(&(F2612->OPN),addr,v); + } + break; + default: /* 0x30-0xff OPN section */ + ym2612_update_one(chip, DUMMYBUF, 0); + /* write register */ + OPNWriteReg(&(F2612->OPN),addr,v); + } + break; + + case 2: /* address port 1 */ + F2612->OPN.ST.address = v; + F2612->addr_A1 = 1; + break; + + case 3: /* data port 1 */ + if (F2612->addr_A1 != 1) + break; /* verified on real YM2608 */ + + addr = F2612->OPN.ST.address; + F2612->REGS[addr | 0x100] = v; + ym2612_update_one(chip, DUMMYBUF, 0); + OPNWriteReg(&(F2612->OPN),addr | 0x100,v); + break; + } + return F2612->OPN.ST.irq; +} + +#if 0 +static UINT8 ym2612_read(void *chip,int a) +{ + YM2612 *F2612 = (YM2612 *)chip; + + switch( a&3) + { + case 0: /* status 0 */ + return FM_STATUS_FLAG(&F2612->OPN.ST); + case 1: + case 2: + case 3: + /* LOG(LOG_WAR,("YM2612 #%p:A=%d read unmapped area\n",F2612->OPN.ST.param,a)); */ + return FM_STATUS_FLAG(&F2612->OPN.ST); + } + return 0; +} + +static int ym2612_timer_over(void *chip,int c) +{ + YM2612 *F2612 = (YM2612 *)chip; + + if( c ) + { /* Timer B */ + TimerBOver( &(F2612->OPN.ST) ); + } + else + { /* Timer A */ + ym2612_update_one(chip, DUMMYBUF, 0); + /* timer update */ + TimerAOver( &(F2612->OPN.ST) ); + /* CSM mode key,TL controll */ + if ((F2612->OPN.ST.mode & 0xc0) == 0x80) + { /* CSM mode total level latch and auto key on */ + CSMKeyControll( &F2612->OPN, &(F2612->CH[2]) ); + } + } + return F2612->OPN.ST.irq; +} +#endif + +static void ym2612_set_mutemask(void *chip, UINT32 MuteMask) +{ + YM2612 *F2612 = (YM2612 *)chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 6; CurChn ++) + F2612->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + F2612->MuteDAC = (MuteMask >> 6) & 0x01; + + return; +} +#if 0 +static void ym2612_setoptions(UINT8 Flags) +{ + PseudoSt = (Flags >> 2) & 0x01; + + return; +} +#endif + +} // Ym2612_MameImpl + + +Ym2612_MAME_Emu::Ym2612_MAME_Emu() { impl = 0; } + +Ym2612_MAME_Emu::~Ym2612_MAME_Emu() +{ + if ( impl ) Ym2612_MameImpl::ym2612_shutdown( impl ); +} + +const char *Ym2612_MAME_Emu::set_rate(double sample_rate, double clock_rate) +{ + if ( impl ) Ym2612_MameImpl::ym2612_shutdown( impl ); + impl = Ym2612_MameImpl::ym2612_init( NULL, static_cast(clock_rate), static_cast(sample_rate), NULL, NULL ); + if ( !impl ) + return "Out of memory"; + return 0; +} + +void Ym2612_MAME_Emu::reset() +{ + if ( impl ) Ym2612_MameImpl::ym2612_reset_chip( impl ); +} + +void Ym2612_MAME_Emu::mute_voices(int mask) +{ + if ( impl ) Ym2612_MameImpl::ym2612_set_mutemask( impl, mask ); +} + +void Ym2612_MAME_Emu::write0(int addr, int data) +{ + if ( !impl ) return; + Ym2612_MameImpl::ym2612_write( impl, 0, static_cast(addr) ); + Ym2612_MameImpl::ym2612_write( impl, 1, static_cast(data) ); +} + +void Ym2612_MAME_Emu::write1(int addr, int data) +{ + if ( !impl ) return; + Ym2612_MameImpl::ym2612_write( impl, 0 + 2, static_cast(addr) ); + Ym2612_MameImpl::ym2612_write( impl, 1 + 2, static_cast(data) ); +} + +void Ym2612_MAME_Emu::run(int pair_count, Ym2612_MAME_Emu::sample_t *out) +{ + if ( impl ) Ym2612_MameImpl::ym2612_generate( impl, out, pair_count, 0); +} diff --git a/game-music-emu/gme/Ym2612_MAME.h b/game-music-emu/gme/Ym2612_MAME.h new file mode 100644 index 000000000..03831065a --- /dev/null +++ b/game-music-emu/gme/Ym2612_MAME.h @@ -0,0 +1,38 @@ +// YM2612 FM sound chip emulator interface + +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ +#ifndef YM2612_EMU_H +#define YM2612_EMU_H + +typedef void Ym2612_MAME_Impl; + +class Ym2612_MAME_Emu { + Ym2612_MAME_Impl* impl; +public: + Ym2612_MAME_Emu(); + ~Ym2612_MAME_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + const char* set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 6 }; + void mute_voices( int mask ); + + // Write addr to register 0 then data to register 1 + void write0( int addr, int data ); + + // Write addr to register 2 then data to register 3 + void write1( int addr, int data ); + + // Run and add pair_count samples into current output buffer contents + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/game-music-emu/gme/Ym2612_Nuked.cpp b/game-music-emu/gme/Ym2612_Nuked.cpp new file mode 100644 index 000000000..fc49ac690 --- /dev/null +++ b/game-music-emu/gme/Ym2612_Nuked.cpp @@ -0,0 +1,1872 @@ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ + +// Based on Nuked OPN2 ym3438.c and ym3438.h + +#include "Ym2612_Nuked.h" + +/* + * Copyright (C) 2017 Alexey Khokholov (Nuke.YKT) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * + * Nuked OPN2(Yamaha YM3438) emulator. + * Thanks: + * Silicon Pr0n: + * Yamaha YM3438 decap and die shot(digshadow). + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.0.7 + */ + + +#include +#include + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint64_t Bit64u; +typedef int64_t Bit64s; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +namespace Ym2612_NukedImpl +{ + +/*EXTRA*/ +#define RSM_FRAC 10 +#define OPN_WRITEBUF_SIZE 2048 +#define OPN_WRITEBUF_DELAY 15 + +enum { + ym3438_type_discrete = 0, /* Discrete YM3438 (Teradrive) */ + ym3438_type_asic = 1, /* ASIC YM3438 (MD1 VA7, MD2, MD3, etc) */ + ym3438_type_ym2612 = 2 /* YM2612 (MD1, MD2 VA2) */ +}; + +/*EXTRA*/ +typedef struct _opn2_writebuf { + Bit64u time; + Bit8u port; + Bit8u data; + Bit8u reserved[6]; +} opn2_writebuf; + +typedef struct +{ + Bit32u cycles; + Bit32u channel; + Bit16s mol, mor; + /* IO */ + Bit16u write_data; + Bit8u write_a; + Bit8u write_d; + Bit8u write_a_en; + Bit8u write_d_en; + Bit8u write_busy; + Bit8u write_busy_cnt; + Bit8u write_fm_address; + Bit8u write_fm_data; + Bit8u write_fm_mode_a; + Bit16u address; + Bit8u data; + Bit8u pin_test_in; + Bit8u pin_irq; + Bit8u busy; + /* LFO */ + Bit8u lfo_en; + Bit8u lfo_freq; + Bit8u lfo_pm; + Bit8u lfo_am; + Bit8u lfo_cnt; + Bit8u lfo_inc; + Bit8u lfo_quotient; + /* Phase generator */ + Bit16u pg_fnum; + Bit8u pg_block; + Bit8u pg_kcode; + Bit32u pg_inc[24]; + Bit32u pg_phase[24]; + Bit8u pg_reset[24]; + Bit32u pg_read; + /* Envelope generator */ + Bit8u eg_cycle; + Bit8u eg_cycle_stop; + Bit8u eg_shift; + Bit8u eg_shift_lock; + Bit8u eg_timer_low_lock; + Bit16u eg_timer; + Bit8u eg_timer_inc; + Bit16u eg_quotient; + Bit8u eg_custom_timer; + Bit8u eg_rate; + Bit8u eg_ksv; + Bit8u eg_inc; + Bit8u eg_ratemax; + Bit8u eg_sl[2]; + Bit8u eg_lfo_am; + Bit8u eg_tl[2]; + Bit8u eg_state[24]; + Bit16u eg_level[24]; + Bit16u eg_out[24]; + Bit8u eg_kon[24]; + Bit8u eg_kon_csm[24]; + Bit8u eg_kon_latch[24]; + Bit8u eg_csm_mode[24]; + Bit8u eg_ssg_enable[24]; + Bit8u eg_ssg_pgrst_latch[24]; + Bit8u eg_ssg_repeat_latch[24]; + Bit8u eg_ssg_hold_up_latch[24]; + Bit8u eg_ssg_dir[24]; + Bit8u eg_ssg_inv[24]; + Bit32u eg_read[2]; + Bit8u eg_read_inc; + /* FM */ + Bit16s fm_op1[6][2]; + Bit16s fm_op2[6]; + Bit16s fm_out[24]; + Bit16u fm_mod[24]; + /* Channel */ + Bit16s ch_acc[6]; + Bit16s ch_out[6]; + Bit16s ch_lock; + Bit8u ch_lock_l; + Bit8u ch_lock_r; + Bit16s ch_read; + /* Timer */ + Bit16u timer_a_cnt; + Bit16u timer_a_reg; + Bit8u timer_a_load_lock; + Bit8u timer_a_load; + Bit8u timer_a_enable; + Bit8u timer_a_reset; + Bit8u timer_a_load_latch; + Bit8u timer_a_overflow_flag; + Bit8u timer_a_overflow; + + Bit16u timer_b_cnt; + Bit8u timer_b_subcnt; + Bit16u timer_b_reg; + Bit8u timer_b_load_lock; + Bit8u timer_b_load; + Bit8u timer_b_enable; + Bit8u timer_b_reset; + Bit8u timer_b_load_latch; + Bit8u timer_b_overflow_flag; + Bit8u timer_b_overflow; + + /* Register set */ + Bit8u mode_test_21[8]; + Bit8u mode_test_2c[8]; + Bit8u mode_ch3; + Bit8u mode_kon_channel; + Bit8u mode_kon_operator[4]; + Bit8u mode_kon[24]; + Bit8u mode_csm; + Bit8u mode_kon_csm; + Bit8u dacen; + Bit16s dacdata; + + Bit8u ks[24]; + Bit8u ar[24]; + Bit8u sr[24]; + Bit8u dt[24]; + Bit8u multi[24]; + Bit8u sl[24]; + Bit8u rr[24]; + Bit8u dr[24]; + Bit8u am[24]; + Bit8u tl[24]; + Bit8u ssg_eg[24]; + + Bit16u fnum[6]; + Bit8u block[6]; + Bit8u kcode[6]; + Bit16u fnum_3ch[6]; + Bit8u block_3ch[6]; + Bit8u kcode_3ch[6]; + Bit8u reg_a4; + Bit8u reg_ac; + Bit8u connect[6]; + Bit8u fb[6]; + Bit8u pan_l[6], pan_r[6]; + Bit8u ams[6]; + Bit8u pms[6]; + + /*EXTRA*/ + Bit32u mute[7]; + Bit32s rateratio; + Bit32s samplecnt; + Bit32s oldsamples[2]; + Bit32s samples[2]; + + Bit64u writebuf_samplecnt; + Bit32u writebuf_cur; + Bit32u writebuf_last; + Bit64u writebuf_lasttime; + opn2_writebuf writebuf[OPN_WRITEBUF_SIZE]; +} ym3438_t; + +/* EXTRA, original was "void OPN2_Reset(ym3438_t *chip)" */ +void OPN2_Reset(ym3438_t *chip, Bit32u rate, Bit32u clock); +void OPN2_SetChipType(Bit32u type); +void OPN2_Clock(ym3438_t *chip, Bit16s *buffer); +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data); +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value); +Bit32u OPN2_ReadTestPin(ym3438_t *chip); +Bit32u OPN2_ReadIRQPin(ym3438_t *chip); +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port); + +/*EXTRA*/ +void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data); +void OPN2_Generate(ym3438_t *chip, Bit16s *buf); +void OPN2_GenerateResampled(ym3438_t *chip, Bit16s *buf); +void OPN2_GenerateStream(ym3438_t *chip, Bit16s *output, Bit32u numsamples); +void OPN2_GenerateStreamMix(ym3438_t *chip, Bit16s *output, Bit32u numsamples); +void OPN2_SetOptions(Bit8u flags); +void OPN2_SetMute(ym3438_t *chip, Bit32u mute); + + + + + +enum { + eg_num_attack = 0, + eg_num_decay = 1, + eg_num_sustain = 2, + eg_num_release = 3 +}; + +/* logsin table */ +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +/* exp table */ +static const Bit16u exprom[256] = { + 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, + 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, + 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, + 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, + 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, + 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, + 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, + 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, + 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, + 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, + 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, + 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, + 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, + 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, + 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, + 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, + 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, + 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, + 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, + 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, + 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, + 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, + 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, + 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, + 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, + 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, + 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, + 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, + 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, + 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, + 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, + 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa +}; + +/* Note table */ +static const Bit32u fn_note[16] = { + 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3 +}; + +/* Envelope generator */ +static const Bit32u eg_stephi[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +static const Bit8u eg_am_shift[4] = { + 7, 3, 1, 0 +}; + +/* Phase generator */ +static const Bit32u pg_detune[8] = { 16, 17, 19, 20, 22, 24, 27, 29 }; + +static const Bit32u pg_lfo_sh1[8][8] = { + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 7, 7, 1, 1 }, + { 7, 7, 7, 7, 1, 1, 1, 1 }, + { 7, 7, 7, 1, 1, 1, 1, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 } +}; + +static const Bit32u pg_lfo_sh2[8][8] = { + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 2, 2, 2, 2 }, + { 7, 7, 7, 2, 2, 2, 7, 7 }, + { 7, 7, 2, 2, 7, 7, 2, 2 }, + { 7, 7, 2, 7, 7, 7, 2, 7 }, + { 7, 7, 7, 2, 7, 7, 2, 1 }, + { 7, 7, 7, 2, 7, 7, 2, 1 }, + { 7, 7, 7, 2, 7, 7, 2, 1 } +}; + +/* Address decoder */ +static const Bit32u op_offset[12] = { + 0x000, /* Ch1 OP1/OP2 */ + 0x001, /* Ch2 OP1/OP2 */ + 0x002, /* Ch3 OP1/OP2 */ + 0x100, /* Ch4 OP1/OP2 */ + 0x101, /* Ch5 OP1/OP2 */ + 0x102, /* Ch6 OP1/OP2 */ + 0x004, /* Ch1 OP3/OP4 */ + 0x005, /* Ch2 OP3/OP4 */ + 0x006, /* Ch3 OP3/OP4 */ + 0x104, /* Ch4 OP3/OP4 */ + 0x105, /* Ch5 OP3/OP4 */ + 0x106 /* Ch6 OP3/OP4 */ +}; + +static const Bit32u ch_offset[6] = { + 0x000, /* Ch1 */ + 0x001, /* Ch2 */ + 0x002, /* Ch3 */ + 0x100, /* Ch4 */ + 0x101, /* Ch5 */ + 0x102 /* Ch6 */ +}; + +/* LFO */ +static const Bit32u lfo_cycles[8] = { + 108, 77, 71, 67, 62, 44, 8, 5 +}; + +/* FM algorithm */ +static const Bit32u fm_algorithm[4][6][8] = { + { + { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_0 */ + { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_1 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 1 } /* Out */ + }, + { + { 0, 1, 0, 0, 0, 1, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 1, 1, 1, 0, 0, 0, 0, 0 }, /* OP2 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 1, 1, 1 } /* Out */ + }, + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ + { 1, 0, 0, 1, 1, 1, 1, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 1, 1, 1, 1 } /* Out */ + }, + { + { 0, 0, 1, 0, 0, 1, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 0, 0, 0, 1, 0, 0, 0, 0 }, /* OP2 */ + { 1, 1, 0, 1, 1, 0, 0, 0 }, /* Last operator */ + { 0, 0, 1, 0, 0, 0, 0, 0 }, /* Last operator */ + { 1, 1, 1, 1, 1, 1, 1, 1 } /* Out */ + } +}; + +static Bit32u chip_type = ym3438_type_discrete; + +void OPN2_DoIO(ym3438_t *chip) +{ + /* Write signal check */ + chip->write_a_en = (chip->write_a & 0x03) == 0x01; + chip->write_d_en = (chip->write_d & 0x03) == 0x01; + chip->write_a <<= 1; + chip->write_d <<= 1; + /* Busy counter */ + chip->busy = chip->write_busy; + chip->write_busy_cnt += chip->write_busy; + chip->write_busy = (chip->write_busy && !(chip->write_busy_cnt >> 5)) || chip->write_d_en; + chip->write_busy_cnt &= 0x1f; +} + +void OPN2_DoRegWrite(ym3438_t *chip) +{ + Bit32u i; + Bit32u slot = chip->cycles % 12; + Bit32u address; + Bit32u channel = chip->channel; + /* Update registers */ + if (chip->write_fm_data) + { + /* Slot */ + if (op_offset[slot] == (chip->address & 0x107)) + { + if (chip->address & 0x08) + { + /* OP2, OP4 */ + slot += 12; + } + address = chip->address & 0xf0; + switch (address) + { + case 0x30: /* DT, MULTI */ + chip->multi[slot] = chip->data & 0x0f; + if (!chip->multi[slot]) + { + chip->multi[slot] = 1; + } + else + { + chip->multi[slot] <<= 1; + } + chip->dt[slot] = (chip->data >> 4) & 0x07; + break; + case 0x40: /* TL */ + chip->tl[slot] = chip->data & 0x7f; + break; + case 0x50: /* KS, AR */ + chip->ar[slot] = chip->data & 0x1f; + chip->ks[slot] = (chip->data >> 6) & 0x03; + break; + case 0x60: /* AM, DR */ + chip->dr[slot] = chip->data & 0x1f; + chip->am[slot] = (chip->data >> 7) & 0x01; + break; + case 0x70: /* SR */ + chip->sr[slot] = chip->data & 0x1f; + break; + case 0x80: /* SL, RR */ + chip->rr[slot] = chip->data & 0x0f; + chip->sl[slot] = (chip->data >> 4) & 0x0f; + chip->sl[slot] |= (chip->sl[slot] + 1) & 0x10; + break; + case 0x90: /* SSG-EG */ + chip->ssg_eg[slot] = chip->data & 0x0f; + break; + default: + break; + } + } + + /* Channel */ + if (ch_offset[channel] == (chip->address & 0x103)) + { + address = chip->address & 0xfc; + switch (address) + { + case 0xa0: + chip->fnum[channel] = (chip->data & 0xff) | ((chip->reg_a4 & 0x07) << 8); + chip->block[channel] = (chip->reg_a4 >> 3) & 0x07; + chip->kcode[channel] = (chip->block[channel] << 2) | fn_note[chip->fnum[channel] >> 7]; + break; + case 0xa4: + chip->reg_a4 = chip->data & 0xff; + break; + case 0xa8: + chip->fnum_3ch[channel] = (chip->data & 0xff) | ((chip->reg_ac & 0x07) << 8); + chip->block_3ch[channel] = (chip->reg_ac >> 3) & 0x07; + chip->kcode_3ch[channel] = (chip->block_3ch[channel] << 2) | fn_note[chip->fnum_3ch[channel] >> 7]; + break; + case 0xac: + chip->reg_ac = chip->data & 0xff; + break; + case 0xb0: + chip->connect[channel] = chip->data & 0x07; + chip->fb[channel] = (chip->data >> 3) & 0x07; + break; + case 0xb4: + chip->pms[channel] = chip->data & 0x07; + chip->ams[channel] = (chip->data >> 4) & 0x03; + chip->pan_l[channel] = (chip->data >> 7) & 0x01; + chip->pan_r[channel] = (chip->data >> 6) & 0x01; + break; + default: + break; + } + } + } + + if (chip->write_a_en || chip->write_d_en) + { + /* Data */ + if (chip->write_a_en) + { + chip->write_fm_data = 0; + } + + if (chip->write_fm_address && chip->write_d_en) + { + chip->write_fm_data = 1; + } + + /* Address */ + if (chip->write_a_en) + { + if ((chip->write_data & 0xf0) != 0x00) + { + /* FM Write */ + chip->address = chip->write_data; + chip->write_fm_address = 1; + } + else + { + /* SSG write */ + chip->write_fm_address = 0; + } + } + + /* FM Mode */ + /* Data */ + if (chip->write_d_en && (chip->write_data & 0x100) == 0) + { + switch (chip->address) + { + case 0x21: /* LSI test 1 */ + for (i = 0; i < 8; i++) + { + chip->mode_test_21[i] = (chip->write_data >> i) & 0x01; + } + break; + case 0x22: /* LFO control */ + if ((chip->write_data >> 3) & 0x01) + { + chip->lfo_en = 0x7f; + } + else + { + chip->lfo_en = 0; + } + chip->lfo_freq = chip->write_data & 0x07; + break; + case 0x24: /* Timer A */ + chip->timer_a_reg &= 0x03; + chip->timer_a_reg |= (chip->write_data & 0xff) << 2; + break; + case 0x25: + chip->timer_a_reg &= 0x3fc; + chip->timer_a_reg |= chip->write_data & 0x03; + break; + case 0x26: /* Timer B */ + chip->timer_b_reg = chip->write_data & 0xff; + break; + case 0x27: /* CSM, Timer control */ + chip->mode_ch3 = (chip->write_data & 0xc0) >> 6; + chip->mode_csm = chip->mode_ch3 == 2; + chip->timer_a_load = chip->write_data & 0x01; + chip->timer_a_enable = (chip->write_data >> 2) & 0x01; + chip->timer_a_reset = (chip->write_data >> 4) & 0x01; + chip->timer_b_load = (chip->write_data >> 1) & 0x01; + chip->timer_b_enable = (chip->write_data >> 3) & 0x01; + chip->timer_b_reset = (chip->write_data >> 5) & 0x01; + break; + case 0x28: /* Key on/off */ + for (i = 0; i < 4; i++) + { + chip->mode_kon_operator[i] = (chip->write_data >> (4 + i)) & 0x01; + } + if ((chip->write_data & 0x03) == 0x03) + { + /* Invalid address */ + chip->mode_kon_channel = 0xff; + } + else + { + chip->mode_kon_channel = (chip->write_data & 0x03) + ((chip->write_data >> 2) & 1) * 3; + } + break; + case 0x2a: /* DAC data */ + chip->dacdata &= 0x01; + chip->dacdata |= (chip->write_data ^ 0x80) << 1; + break; + case 0x2b: /* DAC enable */ + chip->dacen = chip->write_data >> 7; + break; + case 0x2c: /* LSI test 2 */ + for (i = 0; i < 8; i++) + { + chip->mode_test_2c[i] = (chip->write_data >> i) & 0x01; + } + chip->dacdata &= 0x1fe; + chip->dacdata |= chip->mode_test_2c[3]; + chip->eg_custom_timer = !chip->mode_test_2c[7] && chip->mode_test_2c[6]; + break; + default: + break; + } + } + + /* Address */ + if (chip->write_a_en) + { + chip->write_fm_mode_a = chip->write_data & 0xff; + } + } + + if (chip->write_fm_data) + { + chip->data = chip->write_data & 0xff; + } +} + +void OPN2_PhaseCalcIncrement(ym3438_t *chip) +{ + Bit32u chan = chip->channel; + Bit32u slot = chip->cycles; + Bit32u fnum = chip->pg_fnum; + Bit32u fnum_h = fnum >> 4; + Bit32u fm; + Bit32u basefreq; + Bit8u lfo = chip->lfo_pm; + Bit8u lfo_l = lfo & 0x0f; + Bit8u pms = chip->pms[chan]; + Bit8u dt = chip->dt[slot]; + Bit8u dt_l = dt & 0x03; + Bit8u detune = 0; + Bit8u block, note; + Bit8u sum, sum_h, sum_l; + Bit8u kcode = chip->pg_kcode; + + fnum <<= 1; + /* Apply LFO */ + if (lfo_l & 0x08) + { + lfo_l ^= 0x0f; + } + fm = (fnum_h >> pg_lfo_sh1[pms][lfo_l]) + (fnum_h >> pg_lfo_sh2[pms][lfo_l]); + if (pms > 5) + { + fm <<= pms - 5; + } + fm >>= 2; + if (lfo & 0x10) + { + fnum -= fm; + } + else + { + fnum += fm; + } + fnum &= 0xfff; + + basefreq = (fnum << chip->pg_block) >> 2; + + /* Apply detune */ + if (dt_l) + { + if (kcode > 0x1c) + { + kcode = 0x1c; + } + block = kcode >> 2; + note = kcode & 0x03; + sum = block + 9 + ((dt_l == 3) | (dt_l & 0x02)); + sum_h = sum >> 1; + sum_l = sum & 0x01; + detune = pg_detune[(sum_l << 2) | note] >> (9 - sum_h); + } + if (dt & 0x04) + { + basefreq -= detune; + } + else + { + basefreq += detune; + } + basefreq &= 0x1ffff; + chip->pg_inc[slot] = (basefreq * chip->multi[slot]) >> 1; + chip->pg_inc[slot] &= 0xfffff; +} + +void OPN2_PhaseGenerate(ym3438_t *chip) +{ + Bit32u slot; + /* Mask increment */ + slot = (chip->cycles + 20) % 24; + if (chip->pg_reset[slot]) + { + chip->pg_inc[slot] = 0; + } + /* Phase step */ + slot = (chip->cycles + 19) % 24; + chip->pg_phase[slot] += chip->pg_inc[slot]; + chip->pg_phase[slot] &= 0xfffff; + if (chip->pg_reset[slot] || chip->mode_test_21[3]) + { + chip->pg_phase[slot] = 0; + } +} + +void OPN2_EnvelopeSSGEG(ym3438_t *chip) +{ + Bit32u slot = chip->cycles; + Bit8u direction = 0; + chip->eg_ssg_pgrst_latch[slot] = 0; + chip->eg_ssg_repeat_latch[slot] = 0; + chip->eg_ssg_hold_up_latch[slot] = 0; + chip->eg_ssg_inv[slot] = 0; + if (chip->ssg_eg[slot] & 0x08) + { + direction = chip->eg_ssg_dir[slot]; + if (chip->eg_level[slot] & 0x200) + { + /* Reset */ + if ((chip->ssg_eg[slot] & 0x03) == 0x00) + { + chip->eg_ssg_pgrst_latch[slot] = 1; + } + /* Repeat */ + if ((chip->ssg_eg[slot] & 0x01) == 0x00) + { + chip->eg_ssg_repeat_latch[slot] = 1; + } + /* Inverse */ + if ((chip->ssg_eg[slot] & 0x03) == 0x02) + { + direction ^= 1; + } + if ((chip->ssg_eg[slot] & 0x03) == 0x03) + { + direction = 1; + } + } + /* Hold up */ + if (chip->eg_kon_latch[slot] + && ((chip->ssg_eg[slot] & 0x07) == 0x05 || (chip->ssg_eg[slot] & 0x07) == 0x03)) + { + chip->eg_ssg_hold_up_latch[slot] = 1; + } + direction &= chip->eg_kon[slot]; + chip->eg_ssg_inv[slot] = (chip->eg_ssg_dir[slot] ^ ((chip->ssg_eg[slot] >> 2) & 0x01)) + & chip->eg_kon[slot]; + } + chip->eg_ssg_dir[slot] = direction; + chip->eg_ssg_enable[slot] = (chip->ssg_eg[slot] >> 3) & 0x01; +} + +void OPN2_EnvelopeADSR(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 22) % 24; + + Bit8u nkon = chip->eg_kon_latch[slot]; + Bit8u okon = chip->eg_kon[slot]; + Bit8u kon_event; + Bit8u koff_event; + Bit8u eg_off; + Bit16s level; + Bit16s nextlevel = 0; + Bit16s ssg_level; + Bit8u nextstate = chip->eg_state[slot]; + Bit16s inc = 0; + chip->eg_read[0] = chip->eg_read_inc; + chip->eg_read_inc = chip->eg_inc > 0; + + /* Reset phase generator */ + chip->pg_reset[slot] = (nkon && !okon) || chip->eg_ssg_pgrst_latch[slot]; + + /* KeyOn/Off */ + kon_event = (nkon && !okon) || (okon && chip->eg_ssg_repeat_latch[slot]); + koff_event = okon && !nkon; + + ssg_level = level = (Bit16s)chip->eg_level[slot]; + + if (chip->eg_ssg_inv[slot]) + { + /* Inverse */ + ssg_level = 512 - level; + ssg_level &= 0x3ff; + } + if (koff_event) + { + level = ssg_level; + } + if (chip->eg_ssg_enable[slot]) + { + eg_off = level >> 9; + } + else + { + eg_off = (level & 0x3f0) == 0x3f0; + } + nextlevel = level; + if (kon_event) + { + nextstate = eg_num_attack; + /* Instant attack */ + if (chip->eg_ratemax) + { + nextlevel = 0; + } + else if (chip->eg_state[slot] == eg_num_attack && level != 0 && chip->eg_inc && nkon) + { + inc = (~level << chip->eg_inc) >> 5; + } + } + else + { + switch (chip->eg_state[slot]) + { + case eg_num_attack: + if (level == 0) + { + nextstate = eg_num_decay; + } + else if(chip->eg_inc && !chip->eg_ratemax && nkon) + { + inc = (~level << chip->eg_inc) >> 5; + } + break; + case eg_num_decay: + if ((level >> 5) == chip->eg_sl[1]) + { + nextstate = eg_num_sustain; + } + else if (!eg_off && chip->eg_inc) + { + inc = 1 << (chip->eg_inc - 1); + if (chip->eg_ssg_enable[slot]) + { + inc <<= 2; + } + } + break; + case eg_num_sustain: + case eg_num_release: + if (!eg_off && chip->eg_inc) + { + inc = 1 << (chip->eg_inc - 1); + if (chip->eg_ssg_enable[slot]) + { + inc <<= 2; + } + } + break; + default: + break; + } + if (!nkon) + { + nextstate = eg_num_release; + } + } + if (chip->eg_kon_csm[slot]) + { + nextlevel |= chip->eg_tl[1] << 3; + } + + /* Envelope off */ + if (!kon_event && !chip->eg_ssg_hold_up_latch[slot] && chip->eg_state[slot] != eg_num_attack && eg_off) + { + nextstate = eg_num_release; + nextlevel = 0x3ff; + } + + nextlevel += inc; + + chip->eg_kon[slot] = chip->eg_kon_latch[slot]; + chip->eg_level[slot] = (Bit16u)nextlevel & 0x3ff; + chip->eg_state[slot] = nextstate; +} + +void OPN2_EnvelopePrepare(ym3438_t *chip) +{ + Bit8u rate; + Bit8u sum; + Bit8u inc = 0; + Bit32u slot = chip->cycles; + Bit8u rate_sel; + + /* Prepare increment */ + rate = (chip->eg_rate << 1) + chip->eg_ksv; + + if (rate > 0x3f) + { + rate = 0x3f; + } + + sum = ((rate >> 2) + chip->eg_shift_lock) & 0x0f; + if (chip->eg_rate != 0 && chip->eg_quotient == 2) + { + if (rate < 48) + { + switch (sum) + { + case 12: + inc = 1; + break; + case 13: + inc = (rate >> 1) & 0x01; + break; + case 14: + inc = rate & 0x01; + break; + default: + break; + } + } + else + { + inc = eg_stephi[rate & 0x03][chip->eg_timer_low_lock] + (rate >> 2) - 11; + if (inc > 4) + { + inc = 4; + } + } + } + chip->eg_inc = inc; + chip->eg_ratemax = (rate >> 1) == 0x1f; + + /* Prepare rate & ksv */ + rate_sel = chip->eg_state[slot]; + if ((chip->eg_kon[slot] && chip->eg_ssg_repeat_latch[slot]) + || (!chip->eg_kon[slot] && chip->eg_kon_latch[slot])) + { + rate_sel = eg_num_attack; + } + switch (rate_sel) + { + case eg_num_attack: + chip->eg_rate = chip->ar[slot]; + break; + case eg_num_decay: + chip->eg_rate = chip->dr[slot]; + break; + case eg_num_sustain: + chip->eg_rate = chip->sr[slot]; + break; + case eg_num_release: + chip->eg_rate = (chip->rr[slot] << 1) | 0x01; + break; + default: + break; + } + chip->eg_ksv = chip->pg_kcode >> (chip->ks[slot] ^ 0x03); + if (chip->am[slot]) + { + chip->eg_lfo_am = chip->lfo_am >> eg_am_shift[chip->ams[chip->channel]]; + } + else + { + chip->eg_lfo_am = 0; + } + /* Delay TL & SL value */ + chip->eg_tl[1] = chip->eg_tl[0]; + chip->eg_tl[0] = chip->tl[slot]; + chip->eg_sl[1] = chip->eg_sl[0]; + chip->eg_sl[0] = chip->sl[slot]; +} + +void OPN2_EnvelopeGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 23) % 24; + Bit16u level; + + level = chip->eg_level[slot]; + + if (chip->eg_ssg_inv[slot]) + { + /* Inverse */ + level = 512 - level; + } + if (chip->mode_test_21[5]) + { + level = 0; + } + level &= 0x3ff; + + /* Apply AM LFO */ + level += chip->eg_lfo_am; + + /* Apply TL */ + if (!(chip->mode_csm && chip->channel == 2 + 1)) + { + level += chip->eg_tl[0] << 3; + } + if (level > 0x3ff) + { + level = 0x3ff; + } + chip->eg_out[slot] = level; +} + +void OPN2_UpdateLFO(ym3438_t *chip) +{ + if ((chip->lfo_quotient & lfo_cycles[chip->lfo_freq]) == lfo_cycles[chip->lfo_freq]) + { + chip->lfo_quotient = 0; + chip->lfo_cnt++; + } + else + { + chip->lfo_quotient += chip->lfo_inc; + } + chip->lfo_cnt &= chip->lfo_en; +} + +void OPN2_FMPrepare(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 6) % 24; + Bit32u channel = chip->channel; + Bit16s mod, mod1, mod2; + Bit32u op = slot / 6; + Bit8u connect = chip->connect[channel]; + Bit32u prevslot = (chip->cycles + 18) % 24; + + /* Calculate modulation */ + mod1 = mod2 = 0; + + if (fm_algorithm[op][0][connect]) + { + mod2 |= chip->fm_op1[channel][0]; + } + if (fm_algorithm[op][1][connect]) + { + mod1 |= chip->fm_op1[channel][1]; + } + if (fm_algorithm[op][2][connect]) + { + mod1 |= chip->fm_op2[channel]; + } + if (fm_algorithm[op][3][connect]) + { + mod2 |= chip->fm_out[prevslot]; + } + if (fm_algorithm[op][4][connect]) + { + mod1 |= chip->fm_out[prevslot]; + } + mod = mod1 + mod2; + if (op == 0) + { + /* Feedback */ + mod = mod >> (10 - chip->fb[channel]); + if (!chip->fb[channel]) + { + mod = 0; + } + } + else + { + mod >>= 1; + } + chip->fm_mod[slot] = mod; + + slot = (chip->cycles + 18) % 24; + /* OP1 */ + if (slot / 6 == 0) + { + chip->fm_op1[channel][1] = chip->fm_op1[channel][0]; + chip->fm_op1[channel][0] = chip->fm_out[slot]; + } + /* OP2 */ + if (slot / 6 == 2) + { + chip->fm_op2[channel] = chip->fm_out[slot]; + } +} + +void OPN2_ChGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 18) % 24; + Bit32u channel = chip->channel; + Bit32u op = slot / 6; + Bit32u test_dac = chip->mode_test_2c[5]; + Bit16s acc = chip->ch_acc[channel]; + Bit16s add = test_dac; + Bit16s sum = 0; + if (op == 0 && !test_dac) + { + acc = 0; + } + if (fm_algorithm[op][5][chip->connect[channel]] && !test_dac) + { + add += chip->fm_out[slot] >> 5; + } + sum = acc + add; + /* Clamp */ + if (sum > 255) + { + sum = 255; + } + else if(sum < -256) + { + sum = -256; + } + + if (op == 0 || test_dac) + { + chip->ch_out[channel] = chip->ch_acc[channel]; + } + chip->ch_acc[channel] = sum; +} + +void OPN2_ChOutput(ym3438_t *chip) +{ + Bit32u cycles = chip->cycles; + Bit32u slot = chip->cycles; + Bit32u channel = chip->channel; + Bit32u test_dac = chip->mode_test_2c[5]; + Bit16s out; + Bit16s sign; + Bit32u out_en; + chip->ch_read = chip->ch_lock; + if (slot < 12) + { + /* Ch 4,5,6 */ + channel++; + } + if ((cycles & 3) == 0) + { + if (!test_dac) + { + /* Lock value */ + chip->ch_lock = chip->ch_out[channel]; + } + chip->ch_lock_l = chip->pan_l[channel]; + chip->ch_lock_r = chip->pan_r[channel]; + } + /* Ch 6 */ + if (((cycles >> 2) == 1 && chip->dacen) || test_dac) + { + out = (Bit16s)chip->dacdata; + out <<= 7; + out >>= 7; + } + else + { + out = chip->ch_lock; + } + chip->mol = 0; + chip->mor = 0; + + if (chip_type == ym3438_type_ym2612) + { + out_en = ((cycles & 3) == 3) || test_dac; + /* YM2612 DAC emulation(not verified) */ + sign = out >> 8; + if (out >= 0) + { + out++; + sign++; + } + if (chip->ch_lock_l && out_en) + { + chip->mol = out; + } + else + { + chip->mol = sign; + } + if (chip->ch_lock_r && out_en) + { + chip->mor = out; + } + else + { + chip->mor = sign; + } + /* Amplify signal */ + chip->mol *= 3; + chip->mor *= 3; + } + else + { + out_en = ((cycles & 3) != 0) || test_dac; + /* Discrete YM3438 seems has the ladder effect too */ + if (out >= 0 && chip_type == ym3438_type_discrete) + { + out++; + } + if (chip->ch_lock_l && out_en) + { + chip->mol = out; + } + if (chip->ch_lock_r && out_en) + { + chip->mor = out; + } + } +} + +void OPN2_FMGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 19) % 24; + /* Calculate phase */ + Bit16u phase = (chip->fm_mod[slot] + (chip->pg_phase[slot] >> 10)) & 0x3ff; + Bit16u quarter; + Bit16u level; + Bit16s output; + if (phase & 0x100) + { + quarter = (phase ^ 0xff) & 0xff; + } + else + { + quarter = phase & 0xff; + } + level = logsinrom[quarter]; + /* Apply envelope */ + level += chip->eg_out[slot] << 2; + /* Transform */ + if (level > 0x1fff) + { + level = 0x1fff; + } + output = ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 2) >> (level >> 8); + if (phase & 0x200) + { + output = ((~output) ^ (chip->mode_test_21[4] << 13)) + 1; + } + else + { + output = output ^ (chip->mode_test_21[4] << 13); + } + output <<= 2; + output >>= 2; + chip->fm_out[slot] = output; +} + +void OPN2_DoTimerA(ym3438_t *chip) +{ + Bit16u time; + Bit8u load; + load = chip->timer_a_overflow; + if (chip->cycles == 2) + { + /* Lock load value */ + load |= (!chip->timer_a_load_lock && chip->timer_a_load); + chip->timer_a_load_lock = chip->timer_a_load; + if (chip->mode_csm) + { + /* CSM KeyOn */ + chip->mode_kon_csm = load; + } + else + { + chip->mode_kon_csm = 0; + } + } + /* Load counter */ + if (chip->timer_a_load_latch) + { + time = chip->timer_a_reg; + } + else + { + time = chip->timer_a_cnt; + } + chip->timer_a_load_latch = load; + /* Increase counter */ + if ((chip->cycles == 1 && chip->timer_a_load_lock) || chip->mode_test_21[2]) + { + time++; + } + /* Set overflow flag */ + if (chip->timer_a_reset) + { + chip->timer_a_reset = 0; + chip->timer_a_overflow_flag = 0; + } + else + { + chip->timer_a_overflow_flag |= chip->timer_a_overflow & chip->timer_a_enable; + } + chip->timer_a_overflow = (time >> 10); + chip->timer_a_cnt = time & 0x3ff; +} + +void OPN2_DoTimerB(ym3438_t *chip) +{ + Bit16u time; + Bit8u load; + load = chip->timer_b_overflow; + if (chip->cycles == 2) + { + /* Lock load value */ + load |= (!chip->timer_b_load_lock && chip->timer_b_load); + chip->timer_b_load_lock = chip->timer_b_load; + } + /* Load counter */ + if (chip->timer_b_load_latch) + { + time = chip->timer_b_reg; + } + else + { + time = chip->timer_b_cnt; + } + chip->timer_b_load_latch = load; + /* Increase counter */ + if (chip->cycles == 1) + { + chip->timer_b_subcnt++; + } + if ((chip->timer_b_subcnt == 0x10 && chip->timer_b_load_lock) || chip->mode_test_21[2]) + { + time++; + } + chip->timer_b_subcnt &= 0x0f; + /* Set overflow flag */ + if (chip->timer_b_reset) + { + chip->timer_b_reset = 0; + chip->timer_b_overflow_flag = 0; + } + else + { + chip->timer_b_overflow_flag |= chip->timer_b_overflow & chip->timer_b_enable; + } + chip->timer_b_overflow = (time >> 8); + chip->timer_b_cnt = time & 0xff; +} + +void OPN2_KeyOn(ym3438_t*chip) +{ + Bit32u slot = chip->cycles; + Bit32u chan = chip->channel; + /* Key On */ + chip->eg_kon_latch[slot] = chip->mode_kon[slot]; + chip->eg_kon_csm[slot] = 0; + if (chip->channel == 2 && chip->mode_kon_csm) + { + /* CSM Key On */ + chip->eg_kon_latch[slot] = 1; + chip->eg_kon_csm[slot] = 1; + } + if (chip->cycles == chip->mode_kon_channel) + { + /* OP1 */ + chip->mode_kon[chan] = chip->mode_kon_operator[0]; + /* OP2 */ + chip->mode_kon[chan + 12] = chip->mode_kon_operator[1]; + /* OP3 */ + chip->mode_kon[chan + 6] = chip->mode_kon_operator[2]; + /* OP4 */ + chip->mode_kon[chan + 18] = chip->mode_kon_operator[3]; + } +} + +void OPN2_Reset(ym3438_t *chip, Bit32u rate, Bit32u clock) +{ + Bit32u i, rateratio; + rateratio = (Bit32u)chip->rateratio; + memset(chip, 0, sizeof(ym3438_t)); + for (i = 0; i < 24; i++) + { + chip->eg_out[i] = 0x3ff; + chip->eg_level[i] = 0x3ff; + chip->eg_state[i] = eg_num_release; + chip->multi[i] = 1; + } + for (i = 0; i < 6; i++) + { + chip->pan_l[i] = 1; + chip->pan_r[i] = 1; + } + + if (rate != 0) + { + chip->rateratio = (Bit32s)(Bit32u)((((Bit64u)144 * rate) << RSM_FRAC) / clock); + } + else + { + chip->rateratio = (Bit32s)rateratio; + } +} + +void OPN2_SetChipType(Bit32u type) +{ + chip_type = type; +} + +void OPN2_Clock(ym3438_t *chip, Bit16s *buffer) +{ + Bit32u slot = chip->cycles; + chip->lfo_inc = chip->mode_test_21[1]; + chip->pg_read >>= 1; + chip->eg_read[1] >>= 1; + chip->eg_cycle++; + /* Lock envelope generator timer value */ + if (chip->cycles == 1 && chip->eg_quotient == 2) + { + if (chip->eg_cycle_stop) + { + chip->eg_shift_lock = 0; + } + else + { + chip->eg_shift_lock = chip->eg_shift + 1; + } + chip->eg_timer_low_lock = chip->eg_timer & 0x03; + } + /* Cycle specific functions */ + switch (chip->cycles) + { + case 0: + chip->lfo_pm = chip->lfo_cnt >> 2; + if (chip->lfo_cnt & 0x40) + { + chip->lfo_am = chip->lfo_cnt & 0x3f; + } + else + { + chip->lfo_am = chip->lfo_cnt ^ 0x3f; + } + chip->lfo_am <<= 1; + break; + case 1: + chip->eg_quotient++; + chip->eg_quotient %= 3; + chip->eg_cycle = 0; + chip->eg_cycle_stop = 1; + chip->eg_shift = 0; + chip->eg_timer_inc |= chip->eg_quotient >> 1; + chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; + chip->eg_timer_inc = chip->eg_timer >> 12; + chip->eg_timer &= 0xfff; + break; + case 2: + chip->pg_read = chip->pg_phase[21] & 0x3ff; + chip->eg_read[1] = chip->eg_out[0]; + break; + case 13: + chip->eg_cycle = 0; + chip->eg_cycle_stop = 1; + chip->eg_shift = 0; + chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; + chip->eg_timer_inc = chip->eg_timer >> 12; + chip->eg_timer &= 0xfff; + break; + case 23: + chip->lfo_inc |= 1; + break; + } + chip->eg_timer &= ~(chip->mode_test_21[5] << chip->eg_cycle); + if (((chip->eg_timer >> chip->eg_cycle) | (chip->pin_test_in & chip->eg_custom_timer)) & chip->eg_cycle_stop) + { + chip->eg_shift = chip->eg_cycle; + chip->eg_cycle_stop = 0; + } + + OPN2_DoIO(chip); + + OPN2_DoTimerA(chip); + OPN2_DoTimerB(chip); + OPN2_KeyOn(chip); + + OPN2_ChOutput(chip); + OPN2_ChGenerate(chip); + + OPN2_FMPrepare(chip); + OPN2_FMGenerate(chip); + + OPN2_PhaseGenerate(chip); + OPN2_PhaseCalcIncrement(chip); + + OPN2_EnvelopeADSR(chip); + OPN2_EnvelopeGenerate(chip); + OPN2_EnvelopeSSGEG(chip); + OPN2_EnvelopePrepare(chip); + + /* Prepare fnum & block */ + if (chip->mode_ch3) + { + /* Channel 3 special mode */ + switch (slot) + { + case 1: /* OP1 */ + chip->pg_fnum = chip->fnum_3ch[1]; + chip->pg_block = chip->block_3ch[1]; + chip->pg_kcode = chip->kcode_3ch[1]; + break; + case 7: /* OP3 */ + chip->pg_fnum = chip->fnum_3ch[0]; + chip->pg_block = chip->block_3ch[0]; + chip->pg_kcode = chip->kcode_3ch[0]; + break; + case 13: /* OP2 */ + chip->pg_fnum = chip->fnum_3ch[2]; + chip->pg_block = chip->block_3ch[2]; + chip->pg_kcode = chip->kcode_3ch[2]; + break; + case 19: /* OP4 */ + default: + chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; + chip->pg_block = chip->block[(chip->channel + 1) % 6]; + chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; + break; + } + } + else + { + chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; + chip->pg_block = chip->block[(chip->channel + 1) % 6]; + chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; + } + + OPN2_UpdateLFO(chip); + OPN2_DoRegWrite(chip); + chip->cycles = (chip->cycles + 1) % 24; + chip->channel = chip->cycles % 6; + + buffer[0] = chip->mol; + buffer[1] = chip->mor; +} + +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data) +{ + port &= 3; + chip->write_data = ((port << 7) & 0x100) | data; + if (port & 1) + { + /* Data */ + chip->write_d |= 1; + } + else + { + /* Address */ + chip->write_a |= 1; + } +} + +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value) +{ + chip->pin_test_in = value & 1; +} + +Bit32u OPN2_ReadTestPin(ym3438_t *chip) +{ + if (!chip->mode_test_2c[7]) + { + return 0; + } + return chip->cycles == 23; +} + +Bit32u OPN2_ReadIRQPin(ym3438_t *chip) +{ + return chip->timer_a_overflow_flag | chip->timer_b_overflow_flag; +} + +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port) +{ + if ((port & 3) == 0 || chip_type == ym3438_type_asic) + { + if (chip->mode_test_21[6]) + { + /* Read test data */ + Bit32u slot = (chip->cycles + 18) % 24; + Bit16u testdata = ((chip->pg_read & 0x01) << 15) + | ((chip->eg_read[chip->mode_test_21[0]] & 0x01) << 14); + if (chip->mode_test_2c[4]) + { + testdata |= chip->ch_read & 0x1ff; + } + else + { + testdata |= chip->fm_out[slot] & 0x3fff; + } + if (chip->mode_test_21[7]) + { + return testdata & 0xff; + } + else + { + return testdata >> 8; + } + } + else + { + return (Bit8u)(chip->busy << 7) | (Bit8u)(chip->timer_b_overflow_flag << 1) + | (Bit8u)chip->timer_a_overflow_flag; + } + } + return 0; +} + +void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data) +{ + Bit64u time1, time2; + Bit16s buffer[2]; + Bit64u skip; + + if (chip->writebuf[chip->writebuf_last].port & 0x04) + { + OPN2_Write(chip, chip->writebuf[chip->writebuf_last].port & 0X03, + chip->writebuf[chip->writebuf_last].data); + + chip->writebuf_cur = (chip->writebuf_last + 1) % OPN_WRITEBUF_SIZE; + skip = chip->writebuf[chip->writebuf_last].time - chip->writebuf_samplecnt; + chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_last].time; + while (skip--) + { + OPN2_Clock(chip, buffer); + } + } + + chip->writebuf[chip->writebuf_last].port = (port & 0x03) | 0x04; + chip->writebuf[chip->writebuf_last].data = data; + time1 = chip->writebuf_lasttime + OPN_WRITEBUF_DELAY; + time2 = chip->writebuf_samplecnt; + + if (time1 < time2) + { + time1 = time2; + } + + chip->writebuf[chip->writebuf_last].time = time1; + chip->writebuf_lasttime = time1; + chip->writebuf_last = (chip->writebuf_last + 1) % OPN_WRITEBUF_SIZE; +} + +void OPN2_Generate(ym3438_t *chip, Bit16s *buf) +{ + Bit32u i; + Bit16s buffer[2]; + Bit32u mute; + + buf[0] = 0; + buf[1] = 0; + + for (i = 0; i < 24; i++) + { + switch (chip->cycles >> 2) + { + case 0: /* Ch 2 */ + mute = chip->mute[1]; + break; + case 1: /* Ch 6, DAC */ + mute = chip->mute[5 + chip->dacen]; + break; + case 2: /* Ch 4 */ + mute = chip->mute[3]; + break; + case 3: /* Ch 1 */ + mute = chip->mute[0]; + break; + case 4: /* Ch 5 */ + mute = chip->mute[4]; + break; + case 5: /* Ch 3 */ + mute = chip->mute[2]; + break; + default: + mute = 0; + break; + } + OPN2_Clock(chip, buffer); + if (!mute) + { + buf[0] += buffer[0]; + buf[1] += buffer[1]; + } + + while (chip->writebuf[chip->writebuf_cur].time <= chip->writebuf_samplecnt) + { + if (!(chip->writebuf[chip->writebuf_cur].port & 0x04)) + { + break; + } + chip->writebuf[chip->writebuf_cur].port &= 0x03; + OPN2_Write(chip, chip->writebuf[chip->writebuf_cur].port, + chip->writebuf[chip->writebuf_cur].data); + chip->writebuf_cur = (chip->writebuf_cur + 1) % OPN_WRITEBUF_SIZE; + } + chip->writebuf_samplecnt++; + } +} + +void OPN2_GenerateResampled(ym3438_t *chip, Bit16s *buf) +{ + Bit16s buffer[2]; + + while (chip->samplecnt >= chip->rateratio) + { + chip->oldsamples[0] = chip->samples[0]; + chip->oldsamples[1] = chip->samples[1]; + OPN2_Generate(chip, buffer); + chip->samples[0] = buffer[0] * 11; + chip->samples[1] = buffer[1] * 11; + chip->samplecnt -= chip->rateratio; + } + buf[0] = (Bit16s)(((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt) + + chip->samples[0] * chip->samplecnt) / chip->rateratio)>>1); + buf[1] = (Bit16s)(((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt) + + chip->samples[1] * chip->samplecnt) / chip->rateratio)>>1); + chip->samplecnt += 1 << RSM_FRAC; +} + +void OPN2_GenerateStream(ym3438_t *chip, Bit16s *output, Bit32u numsamples) +{ + Bit32u i; + Bit16s buffer[2]; + + for (i = 0; i < numsamples; i++) + { + OPN2_GenerateResampled(chip, buffer); + *output++ = buffer[0]; + *output++ = buffer[1]; + } +} + +void OPN2_GenerateStreamMix(ym3438_t *chip, Bit16s *output, Bit32u numsamples) +{ + Bit32u i; + Bit16s buffer[2]; + + for (i = 0; i < numsamples; i++) + { + OPN2_GenerateResampled(chip, buffer); + *output++ += buffer[0]; + *output++ += buffer[1]; + } +} + + +void OPN2_SetOptions(Bit8u flags) +{ + switch ((flags >> 3) & 0x03) + { + case 0x00: /* YM2612 */ + default: + OPN2_SetChipType(ym3438_type_ym2612); + break; + case 0x01: /* ASIC YM3438 */ + OPN2_SetChipType(ym3438_type_asic); + break; + case 0x02: /* Discrete YM3438 */ + OPN2_SetChipType(ym3438_type_discrete); + break; + } +} + +void OPN2_SetMute(ym3438_t *chip, Bit32u mute) +{ + Bit32u i; + for (i = 0; i < 7; i++) + { + chip->mute[i] = (mute >> i) & 0x01; + } +} + + +} // Ym2612_NukedImpl + + +Ym2612_Nuked_Emu::Ym2612_Nuked_Emu() +{ + Ym2612_NukedImpl::OPN2_SetChipType( Ym2612_NukedImpl::ym3438_type_asic ); + impl = new Ym2612_NukedImpl::ym3438_t; +} + +Ym2612_Nuked_Emu::~Ym2612_Nuked_Emu() +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( chip_r ) delete chip_r; +} + +const char *Ym2612_Nuked_Emu::set_rate(double sample_rate, double clock_rate) +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( !chip_r ) + return "Out of memory"; + prev_sample_rate = sample_rate; + prev_clock_rate = clock_rate; + Ym2612_NukedImpl::OPN2_Reset( chip_r, static_cast(sample_rate), static_cast(clock_rate) ); + return 0; +} + +void Ym2612_Nuked_Emu::reset() +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( !chip_r ) Ym2612_NukedImpl::OPN2_Reset( chip_r, static_cast(prev_sample_rate), static_cast(prev_clock_rate) ); +} + +void Ym2612_Nuked_Emu::mute_voices(int mask) +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( chip_r ) Ym2612_NukedImpl::OPN2_SetMute( chip_r, mask ); +} + +void Ym2612_Nuked_Emu::write0(int addr, int data) +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( !chip_r ) return; + Ym2612_NukedImpl::OPN2_WriteBuffered( chip_r, 0, static_cast(addr) ); + Ym2612_NukedImpl::OPN2_WriteBuffered( chip_r, 1, static_cast(data) ); +} + +void Ym2612_Nuked_Emu::write1(int addr, int data) +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( !chip_r ) return; + Ym2612_NukedImpl::OPN2_WriteBuffered( chip_r, 0 + 2, static_cast(addr) ); + Ym2612_NukedImpl::OPN2_WriteBuffered( chip_r, 1 + 2, static_cast(data) ); +} + +void Ym2612_Nuked_Emu::run(int pair_count, Ym2612_Nuked_Emu::sample_t *out) +{ + Ym2612_NukedImpl::ym3438_t *chip_r = reinterpret_cast(impl); + if ( !chip_r ) return; + Ym2612_NukedImpl::OPN2_GenerateStream(chip_r, out, pair_count); +} diff --git a/game-music-emu/gme/Ym2612_Nuked.h b/game-music-emu/gme/Ym2612_Nuked.h new file mode 100644 index 000000000..6c265b138 --- /dev/null +++ b/game-music-emu/gme/Ym2612_Nuked.h @@ -0,0 +1,41 @@ +// YM2612 FM sound chip emulator interface + +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ +#ifndef YM2612_EMU_H +#define YM2612_EMU_H + +typedef void Ym2612_Nuked_Impl; + +class Ym2612_Nuked_Emu { + Ym2612_Nuked_Impl* impl; + double prev_sample_rate; + double prev_clock_rate; +public: + Ym2612_Nuked_Emu(); + ~Ym2612_Nuked_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + const char* set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 6 }; + void mute_voices( int mask ); + + // Write addr to register 0 then data to register 1 + void write0( int addr, int data ); + + // Write addr to register 2 then data to register 3 + void write1( int addr, int data ); + + // Run and add pair_count samples into current output buffer contents + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif + diff --git a/game-music-emu/gme/blargg_common.h b/game-music-emu/gme/blargg_common.h index ed218a8da..13cc2417e 100644 --- a/game-music-emu/gme/blargg_common.h +++ b/game-music-emu/gme/blargg_common.h @@ -80,6 +80,9 @@ public: #define BLARGG_4CHAR( a, b, c, d ) \ ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) +#define BLARGG_2CHAR( a, b ) \ + ((a&0xFF)*0x100L + (b&0xFF)) + // BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. #ifndef BOOST_STATIC_ASSERT #ifdef _MSC_VER @@ -132,51 +135,12 @@ public: typedef unsigned blargg_ulong; #endif -// BOOST::int8_t etc. +// int8_t etc. -// HAVE_STDINT_H: If defined, use for int8_t etc. -#if defined (HAVE_STDINT_H) +// TODO: Add CMake check for this, although I'd likely just point affected +// persons to a real compiler... +#if 1 || defined (HAVE_STDINT_H) #include - #define BOOST - -// HAVE_INTTYPES_H: If defined, use for int8_t etc. -#elif defined (HAVE_INTTYPES_H) - #include - #define BOOST - -#else - struct BOOST - { - #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F - typedef signed char int8_t; - typedef unsigned char uint8_t; - #else - // No suitable 8-bit type available - typedef struct see_blargg_common_h int8_t; - typedef struct see_blargg_common_h uint8_t; - #endif - - #if USHRT_MAX == 0xFFFF - typedef short int16_t; - typedef unsigned short uint16_t; - #else - // No suitable 16-bit type available - typedef struct see_blargg_common_h int16_t; - typedef struct see_blargg_common_h uint16_t; - #endif - - #if ULONG_MAX == 0xFFFFFFFF - typedef long int32_t; - typedef unsigned long uint32_t; - #elif UINT_MAX == 0xFFFFFFFF - typedef int int32_t; - typedef unsigned int uint32_t; - #else - // No suitable 32-bit type available - typedef struct see_blargg_common_h int32_t; - typedef struct see_blargg_common_h uint32_t; - #endif - }; #endif #if __GNUC__ >= 3 diff --git a/game-music-emu/gme/blargg_endian.h b/game-music-emu/gme/blargg_endian.h index ba09e067e..46e58e2f0 100644 --- a/game-music-emu/gme/blargg_endian.h +++ b/game-music-emu/gme/blargg_endian.h @@ -123,15 +123,15 @@ inline void set_be32( void* p, blargg_ulong n ) #if BLARGG_NONPORTABLE // Optimized implementation if byte order is known #if BLARGG_LITTLE_ENDIAN - #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) - #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) - #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) - #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #define GET_LE16( addr ) (*(uint16_t*) (addr)) + #define GET_LE32( addr ) (*(uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(uint32_t*) (addr) = (data)) #elif BLARGG_BIG_ENDIAN - #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) - #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) - #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) - #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #define GET_BE16( addr ) (*(uint16_t*) (addr)) + #define GET_BE32( addr ) (*(uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(uint32_t*) (addr) = (data)) #if BLARGG_CPU_POWERPC // PowerPC has special byte-reversed instructions @@ -172,13 +172,13 @@ inline void set_be32( void* p, blargg_ulong n ) // auto-selecting versions -inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } -inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } -inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } -inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } -inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } -inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } -inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } -inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } +inline void set_le( uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( uint32_t* p ) { return GET_BE32( p ); } #endif diff --git a/game-music-emu/gme/blargg_source.h b/game-music-emu/gme/blargg_source.h index b011777ad..b65afd30b 100644 --- a/game-music-emu/gme/blargg_source.h +++ b/game-music-emu/gme/blargg_source.h @@ -18,6 +18,19 @@ all other #include lines. */ #undef require #define require( expr ) assert( expr ) +// Use to provide hints to compiler for optimized code layout in situations where we +// can almost always expect a conditional to go one way or the other. Should only be +// used in situations where an unexpected branch is truly exceptional though! +#undef likely +#undef unlikely +#ifdef __GNUC__ + #define likely( x ) __builtin_expect(x, 1) + #define unlikely( x ) __builtin_expect(x, 0) +#else + #define likely( x ) (x) + #define unlikely( x ) (x) +#endif + // Like printf() except output goes to debug log file. Might be defined to do // nothing (not even evaluate its arguments). // void debug_printf( const char* format, ... ); diff --git a/game-music-emu/gme/gme.cpp b/game-music-emu/gme/gme.cpp index 5247c1f1a..292cbff4c 100644 --- a/game-music-emu/gme/gme.cpp +++ b/game-music-emu/gme/gme.cpp @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ #include "Music_Emu.h" @@ -83,6 +83,8 @@ BLARGG_EXPORT const char* gme_identify_header( void const* header ) case BLARGG_4CHAR('S','N','E','S'): return "SPC"; case BLARGG_4CHAR('V','g','m',' '): return "VGM"; } + if (get_be16(header) == BLARGG_2CHAR(0x1F, 0x8B)) + return "VGZ"; return ""; } @@ -111,6 +113,14 @@ BLARGG_EXPORT gme_type_t gme_identify_extension( const char* extension_ ) return 0; } +BLARGG_EXPORT const char *gme_type_extension( gme_type_t music_type ) +{ + const gme_type_t_ *const music_typeinfo = static_cast( music_type ); + if ( music_type ) + return music_typeinfo->extension_; + return ""; +} + BLARGG_EXPORT gme_err_t gme_identify_file( const char* path, gme_type_t* type_out ) { *type_out = gme_identify_extension( path ); @@ -187,7 +197,8 @@ BLARGG_EXPORT gme_err_t gme_open_file( const char* path, Music_Emu** out, int sa return err; } -BLARGG_EXPORT Music_Emu* gme_new_emu( gme_type_t type, int rate ) +// Used to implement gme_new_emu and gme_new_emu_multi_channel +Music_Emu* gme_internal_new_emu_( gme_type_t type, int rate, bool multi_channel ) { if ( type ) { @@ -198,9 +209,18 @@ BLARGG_EXPORT Music_Emu* gme_new_emu( gme_type_t type, int rate ) if ( me ) { #if !GME_DISABLE_STEREO_DEPTH + me->set_multi_channel( multi_channel ); + if ( type->flags_ & 1 ) { - me->effects_buffer = BLARGG_NEW Effects_Buffer; + if ( me->multi_channel() ) + { + me->effects_buffer = BLARGG_NEW Effects_Buffer(8); + } + else + { + me->effects_buffer = BLARGG_NEW Effects_Buffer(1); + } if ( me->effects_buffer ) me->set_buffer( me->effects_buffer ); } @@ -220,6 +240,17 @@ BLARGG_EXPORT Music_Emu* gme_new_emu( gme_type_t type, int rate ) return 0; } +BLARGG_EXPORT Music_Emu* gme_new_emu( gme_type_t type, int rate ) +{ + return gme_internal_new_emu_( type, rate, false /* no multichannel */); +} + +BLARGG_EXPORT Music_Emu* gme_new_emu_multi_channel( gme_type_t type, int rate ) +{ + // multi-channel emulator (if possible, not all emu types support multi-channel) + return gme_internal_new_emu_( type, rate, true /* multichannel */); +} + BLARGG_EXPORT gme_err_t gme_load_file( Music_Emu* me, const char* path ) { return me->load_file( path ); } BLARGG_EXPORT gme_err_t gme_load_data( Music_Emu* me, void const* data, long size ) @@ -345,9 +376,10 @@ BLARGG_EXPORT void gme_ignore_silence ( Music_Emu* me, int disable ) BLARGG_EXPORT void gme_set_tempo ( Music_Emu* me, double t ) { me->set_tempo( t ); } BLARGG_EXPORT void gme_mute_voice ( Music_Emu* me, int index, int mute ) { me->mute_voice( index, mute != 0 ); } BLARGG_EXPORT void gme_mute_voices ( Music_Emu* me, int mask ) { me->mute_voices( mask ); } -BLARGG_EXPORT void gme_enable_accuracy( Music_Emu* me, int enabled ) { me->enable_accuracy( !!enabled ); } +BLARGG_EXPORT void gme_enable_accuracy( Music_Emu* me, int enabled ) { me->enable_accuracy( enabled ); } BLARGG_EXPORT void gme_clear_playlist ( Music_Emu* me ) { me->clear_playlist(); } BLARGG_EXPORT int gme_type_multitrack( gme_type_t t ) { return t->track_count != 1; } +BLARGG_EXPORT int gme_multi_channel ( Music_Emu const* me ) { return me->multi_channel(); } BLARGG_EXPORT void gme_set_equalizer ( Music_Emu* me, gme_equalizer_t const* eq ) { diff --git a/game-music-emu/gme/gme.h b/game-music-emu/gme/gme.h index cb07061b4..80c6ce846 100644 --- a/game-music-emu/gme/gme.h +++ b/game-music-emu/gme/gme.h @@ -1,6 +1,6 @@ /* Game music emulator library C interface (also usable from C++) */ -/* Game_Music_Emu 0.6.1 */ +/* Game_Music_Emu 0.6.2 */ #ifndef GME_H #define GME_H @@ -8,7 +8,7 @@ extern "C" { #endif -#define GME_VERSION 0x000601 /* 1 byte major, 1 byte minor, 1 byte patch-level */ +#define GME_VERSION 0x000602 /* 1 byte major, 1 byte minor, 1 byte patch-level */ /* Error string returned by library functions, or NULL if no error (success) */ typedef const char* gme_err_t; @@ -187,13 +187,18 @@ const char* gme_type_system( gme_type_t ); /* True if this music file type supports multiple tracks */ int gme_type_multitrack( gme_type_t ); +/* whether the pcm output retrieved by gme_play() will have all 8 voices rendered to their + * individual stereo channel or (if false) these voices get mixed into one single stereo channel + * @since 0.6.2 */ +int gme_multi_channel( Music_Emu const* ); /******** Advanced file loading ********/ /* Error returned if file type is not supported */ extern const char* const gme_wrong_file_type; -/* Same as gme_open_file(), but uses file data already in memory. Makes copy of data. */ +/* Same as gme_open_file(), but uses file data already in memory. Makes copy of data. + * The resulting Music_Emu object will be set to single channel mode. */ gme_err_t gme_open_data( void const* data, long size, Music_Emu** out, int sample_rate ); /* Determine likely game music type based on first four bytes of file. Returns @@ -204,6 +209,14 @@ const char* gme_identify_header( void const* header ); /* Get corresponding music type for file path or extension passed in. */ gme_type_t gme_identify_extension( const char path_or_extension [] ); +/** + * Get typical file extension for a given music type. This is not a replacement + * for a file content identification library (but see gme_identify_header). + * + * @since 0.6.2 + */ +const char* gme_type_extension( gme_type_t music_type ); + /* Determine file type based on file's extension or header (if extension isn't recognized). Sets *type_out to type, or 0 if unrecognized or error. */ gme_err_t gme_identify_file( const char path [], gme_type_t* type_out ); @@ -212,6 +225,13 @@ gme_err_t gme_identify_file( const char path [], gme_type_t* type_out ); track information, pass gme_info_only for sample_rate. */ Music_Emu* gme_new_emu( gme_type_t, int sample_rate ); +/* Create new multichannel emulator and set sample rate. Returns NULL if out of memory. + * If you only need track information, pass gme_info_only for sample_rate. + * (see gme_multi_channel for more information on multichannel support) + * @since 0.6.2 + */ +Music_Emu* gme_new_emu_multi_channel( gme_type_t, int sample_rate ); + /* Load music file into emulator */ gme_err_t gme_load_file( Music_Emu*, const char path [] ); diff --git a/game-music-emu/gme/gme_types.h b/game-music-emu/gme/gme_types.h old mode 100755 new mode 100644 index bd3670e64..06226f4aa --- a/game-music-emu/gme/gme_types.h +++ b/game-music-emu/gme/gme_types.h @@ -1,13 +1,11 @@ #ifndef GME_TYPES_H #define GME_TYPES_H -/* CMake will either define the following to 1, or #undef it, - * depending on the options passed to CMake. This is used to - * conditionally compile in the various emulator types. - * - * See gme_type_list() in gme.cpp +/* + * This is a default gme_types.h for use when *not* using + * CMake. If CMake is in use gme_types.h.in will be + * processed instead. */ - #define USE_GME_AY #define USE_GME_GBS #define USE_GME_GYM diff --git a/game-music-emu/gme/libgme.pc.in b/game-music-emu/gme/libgme.pc.in new file mode 100644 index 000000000..f057ce17c --- /dev/null +++ b/game-music-emu/gme/libgme.pc.in @@ -0,0 +1,16 @@ +# entries grouped with CMake are expanded by CMake +# ${foo} entries are left alone by CMake and much +# later are used by pkg-config. +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +lib_suffix=@LIB_SUFFIX@ +libdir=${exec_prefix}/lib${lib_suffix} +includedir=${prefix}/include + +Name: Game_Music_Emu +Description: A video game emulation library for music. +URL: https://bitbucket.org/mpyne/game-music-emu/wiki/Home +Version: @GME_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lgme +Libs.private: -lstdc++ @PKG_CONFIG_ZLIB@ diff --git a/game-music-emu/readme.txt b/game-music-emu/readme.txt index b29a00797..22cc20aad 100644 --- a/game-music-emu/readme.txt +++ b/game-music-emu/readme.txt @@ -1,4 +1,4 @@ -Game_Music_Emu 0.6.1: Game Music Emulators +Game_Music_Emu 0.6.2: Game Music Emulators ------------------------------------------ Game_Music_Emu is a collection of video game music file emulators that support the following formats and systems: @@ -34,10 +34,12 @@ several architectures, Mac OS, MorphOS, Xbox, PlayStation Portable, GP2X, and Nintendo DS. Author : Shay Green -Website: http://www.slack.net/~ant/ -Forum : http://groups.google.com/group/blargg-sound-libs +Website: https://bitbucket.org/mpyne/game-music-emu/wiki/Home License: GNU Lesser General Public License (LGPL) +Note: When you will use MAME YM2612 emulator, the license of library +will be GNU General Public License (GPL) v2.0+! + Current Maintainer: Michael Pyne Getting Started @@ -191,8 +193,13 @@ gme/ Sms_Apu.cpp Common Sega emulator files Sms_Apu.h Sms_Oscs.h - Ym2612_Emu.cpp Ym2612_Emu.h + Ym2612_GENS.cpp GENS 2.10 YM2612 emulator (LGPLv2.1+ license) + Ym2612_GENS.h + Ym2612_MAME.cpp MAME YM2612 emulator (GPLv2.0+ license) + Ym2612_MAME.h + Ym2612_Nuked.cpp Nuked OPN2 emulator (LGPLv2.1+ license) + Ym2612_Nuked.h Dual_Resampler.cpp Dual_Resampler.h Fir_Resampler.cpp @@ -219,7 +226,7 @@ gme/ Multi_Buffer.cpp Data_Reader.h Data_Reader.cpp - + CMakeLists.txt CMake build rules @@ -227,6 +234,8 @@ Legal ----- Game_Music_Emu library copyright (C) 2003-2009 Shay Green. Sega Genesis YM2612 emulator copyright (C) 2002 Stephane Dallongeville. +MAME YM2612 emulator copyright (C) 2003 Jarek Burczynski, Tatsuyuki Satoh +Nuked OPN2 emulator copyright (C) 2017 Alexey Khokholov (Nuke.YKT) -- Shay Green diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 84dcd4d29..e26044dc6 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -141,6 +141,11 @@ Note: All fields default to false unless mentioned otherwise. // 13: Portal line. revealed = ; // true = line is initially visible on automap. + health = ; // Amount of hitpoints for this line. + healthgroup = ; // ID of destructible object to synchronize hitpoints (optional, default is 0) + damagespecial = ; // This line will call special if having health>0 and receiving damage + deathspecial = ; // This line will call special if health was reduced to 0 + * Note about arg0str For lines with ACS specials (80-86 and 226), if arg0str is present and non-null, it @@ -248,17 +253,21 @@ Note: All fields default to false unless mentioned otherwise. color_sprites = ; // Material color of sprites in sector. Default is white (0xffffff) - portal_ceil_blocksound = // ceiling portal blocks sound. - portal_ceil_disabled = // ceiling portal disabled. - portal_ceil_nopass = // ceiling portal blocks movement if true. - portal_ceil_norender = // ceiling portal not rendered. - portal_ceil_overlaytype = // defines translucency style, can either be "translucent" or "additive". Default is "translucent". - portal_floor_blocksound = // floor portal blocks sound. - portal_floor_disabled = // floor portal disabled. - portal_floor_nopass = // ceiling portal blocks movement if true. - portal_floor_norender = // ceiling portal not rendered. - portal_floor_overlaytype = // defines translucency style, can either be "translucent" or "additive". Default is "translucent". + portal_ceil_blocksound = ; // ceiling portal blocks sound. + portal_ceil_disabled = ; // ceiling portal disabled. + portal_ceil_nopass = ; // ceiling portal blocks movement if true. + portal_ceil_norender = ; // ceiling portal not rendered. + portal_ceil_overlaytype = ; // defines translucency style, can either be "translucent" or "additive". Default is "translucent". + portal_floor_blocksound = ; // floor portal blocks sound. + portal_floor_disabled = ; // floor portal disabled. + portal_floor_nopass = ; // ceiling portal blocks movement if true. + portal_floor_norender = ; // ceiling portal not rendered. + portal_floor_overlaytype = ; // defines translucency style, can either be "translucent" or "additive". Default is "translucent". + healthfloor = ; // Amount of hitpoints for this sector (includes floor and bottom-outside linedef sides) + healthfloorgroup = ; // ID of destructible object to synchronize hitpoints (optional, default is 0) + healthceiling = ; // Amount of hitpoints for this sector (includes ceiling and top-outside linedef sides) + healthceilinggroup = ; // ID of destructible object to synchronize hitpoints (optional, default is 0) * Note about dropactors diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f0ee7dbbd..ef730ed21 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -694,13 +694,12 @@ file( GLOB HEADER_FILES polyrenderer/scene/*.h hwrenderer/data/*.h hwrenderer/dynlights/*.h + hwrenderer/models/*.h hwrenderer/postprocessing/*.h hwrenderer/scene/*.h hwrenderer/textures/*.h hwrenderer/utility/*.h gl/*.h - gl/data/*.h - gl/dynlights/*.h gl/models/*.h gl/renderer/*.h gl/scene/*.h @@ -820,14 +819,6 @@ set( FASTMATH_SOURCES textures/hires/hqnx/hq4x.cpp textures/hires/xbr/xbrz.cpp textures/hires/xbr/xbrz_old.cpp - gl/scene/gl_drawinfo.cpp - gl/scene/gl_flats.cpp - gl/scene/gl_sprite.cpp - gl/scene/gl_skydome.cpp - gl/scene/gl_weapon.cpp - gl/scene/gl_scene.cpp - gl/scene/gl_portal.cpp - gl/scene/gl_walls_draw.cpp gl_load/gl_load.c hwrenderer/postprocessing/hw_postprocess_cvars.cpp hwrenderer/postprocessing/hw_postprocessshader.cpp @@ -842,6 +833,7 @@ set( FASTMATH_SOURCES hwrenderer/scene/hw_portal.cpp hwrenderer/scene/hw_renderhacks.cpp hwrenderer/scene/hw_sky.cpp + hwrenderer/scene/hw_skyportal.cpp hwrenderer/scene/hw_sprites.cpp hwrenderer/scene/hw_spritelight.cpp hwrenderer/scene/hw_walls.cpp @@ -956,6 +948,7 @@ set (PCH_SOURCES p_actionfunctions.cpp p_ceiling.cpp p_conversation.cpp + p_destructible.cpp p_doors.cpp p_effect.cpp p_enemy.cpp @@ -1040,32 +1033,31 @@ set (PCH_SOURCES g_statusbar/sbarinfo.cpp g_statusbar/sbar_mugshot.cpp g_statusbar/shared_sbar.cpp - gl/data/gl_vertexbuffer.cpp - gl/data/gl_uniformbuffer.cpp - gl/data/gl_viewpointbuffer.cpp - gl/dynlights/gl_lightbuffer.cpp - gl/dynlights/gl_shadowmap.cpp - gl/models/gl_models.cpp - gl/renderer/gl_quaddrawer.cpp gl/renderer/gl_renderer.cpp gl/renderer/gl_renderstate.cpp gl/renderer/gl_renderbuffers.cpp - gl/renderer/gl_lightdata.cpp gl/renderer/gl_postprocess.cpp gl/renderer/gl_postprocessstate.cpp gl/renderer/gl_stereo3d.cpp + gl/renderer/gl_scene.cpp gl/shaders/gl_shader.cpp gl/shaders/gl_shaderprogram.cpp gl/shaders/gl_postprocessshader.cpp gl_load/gl_interface.cpp gl/system/gl_framebuffer.cpp gl/system/gl_debug.cpp + gl/system/gl_buffers.cpp gl/textures/gl_hwtexture.cpp gl/textures/gl_samplers.cpp hwrenderer/data/flatvertices.cpp + hwrenderer/data/hw_viewpointbuffer.cpp hwrenderer/dynlights/hw_aabbtree.cpp hwrenderer/dynlights/hw_shadowmap.cpp + hwrenderer/dynlights/hw_lightbuffer.cpp + hwrenderer/models/hw_models.cpp hwrenderer/scene/hw_skydome.cpp + hwrenderer/scene/hw_drawlistadd.cpp + hwrenderer/scene/hw_renderstate.cpp hwrenderer/postprocessing/hw_postprocess.cpp hwrenderer/postprocessing/hw_postprocess_cvars.cpp hwrenderer/postprocessing/hw_postprocessshader.cpp @@ -1076,6 +1068,7 @@ set (PCH_SOURCES hwrenderer/textures/hw_precache.cpp hwrenderer/utility/hw_clock.cpp hwrenderer/utility/hw_cvars.cpp + hwrenderer/utility/hw_draw2d.cpp hwrenderer/utility/hw_lighting.cpp hwrenderer/utility/hw_shaderpatcher.cpp hwrenderer/utility/hw_vrmodes.cpp @@ -1430,7 +1423,6 @@ source_group("OpenGL Loader" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl source_group("OpenGL Renderer" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/.+") source_group("OpenGL Renderer\\Data" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/data/.+") source_group("OpenGL Renderer\\Dynamic Lights" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/dynlights/.+") -source_group("OpenGL Renderer\\Models" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/models/.+") source_group("OpenGL Renderer\\Renderer" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/renderer/.+") source_group("OpenGL Renderer\\Scene" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/scene/.+") source_group("OpenGL Renderer\\Shaders" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/shaders/.+") diff --git a/src/actionspecials.h b/src/actionspecials.h index 7d1ac1d98..d62f93536 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -152,6 +152,8 @@ DEFINE_SPECIAL(Sector_Set3DFloor, 160, -1, -1, 5) DEFINE_SPECIAL(Sector_SetContents, 161, -1, -1, 3) // [RH] Begin new specials for ZDoom +DEFINE_SPECIAL(Line_SetHealth, 150, 2, 2, 2) +DEFINE_SPECIAL(Sector_SetHealth, 151, 3, 3, 3) DEFINE_SPECIAL(Ceiling_CrushAndRaiseDist, 168, 3, 5, 5) DEFINE_SPECIAL(Generic_Crusher2, 169, 5, 5, 5) DEFINE_SPECIAL(Sector_SetCeilingScale2, 170, 3, 3, 3) diff --git a/src/actor.h b/src/actor.h index 7fb4e4cc8..043da9b7c 100644 --- a/src/actor.h +++ b/src/actor.h @@ -1182,6 +1182,8 @@ public: AActor *BlockingMobj; // Actor that blocked the last move line_t *BlockingLine; // Line that blocked the last move + sector_t *BlockingCeiling; // Sector that blocked the last move (ceiling plane slope) + sector_t *BlockingFloor; // Sector that blocked the last move (floor plane slope) int PoisonDamage; // Damage received per tic from poison. FName PoisonDamageType; // Damage type dealt by poison. diff --git a/src/bbannouncer.cpp b/src/bbannouncer.cpp index 144528cd7..80de8b979 100644 --- a/src/bbannouncer.cpp +++ b/src/bbannouncer.cpp @@ -59,7 +59,7 @@ struct SoundAndString // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- -void SexMessage (const char *from, char *to, int gender, +void PronounMessage (const char *from, char *to, int pronoun, const char *victim, const char *killer); // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- @@ -266,7 +266,7 @@ bool AnnounceKill (AActor *killer, AActor *killee) { char assembled[1024]; - SexMessage (message, assembled, killee->player->userinfo.GetGender(), + PronounMessage (message, assembled, killee->player->userinfo.GetGender(), killee->player->userinfo.GetName(), killerName); Printf (PRINT_MEDIUM, "%s\n", assembled); } @@ -298,7 +298,7 @@ bool AnnounceTelefrag (AActor *killer, AActor *killee) { char assembled[1024]; - SexMessage (message, assembled, killee->player->userinfo.GetGender(), + PronounMessage (message, assembled, killee->player->userinfo.GetGender(), killee->player->userinfo.GetName(), killer->player->userinfo.GetName()); Printf (PRINT_MEDIUM, "%s\n", assembled); } diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index a143e55e0..18be473e8 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -1194,13 +1194,12 @@ static void PrintSecretString(const char *string, bool thislevel) else colstr = TEXTCOLOR_GREEN; } } - FBrokenLines *brok = V_BreakLines(ConFont, screen->GetWidth()*95/100, string); + auto brok = V_BreakLines(ConFont, screen->GetWidth()*95/100, string); - for (int k = 0; brok[k].Width >= 0; k++) + for (auto &line : brok) { - Printf("%s%s\n", colstr, brok[k].Text.GetChars()); + Printf("%s%s\n", colstr, line.Text.GetChars()); } - V_FreeBrokenLines(brok); } } diff --git a/src/c_console.cpp b/src/c_console.cpp index bc552c3b8..eb078033e 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -732,8 +732,8 @@ void FNotifyBuffer::Shift(int maxlines) void FNotifyBuffer::AddString(int printlevel, FString source) { - FBrokenLines *lines; - int i, width; + TArray lines; + int width; if ((printlevel != 128 && !show_messages) || source.IsEmpty() || @@ -764,14 +764,14 @@ void FNotifyBuffer::AddString(int printlevel, FString source) } } - if (lines == NULL) + if (lines.Size() == 0) return; - for (i = 0; lines[i].Width >= 0; i++) + for (auto &line : lines) { FNotifyText newline; - newline.Text = lines[i].Text; + newline.Text = line.Text; newline.TimeOut = gametic + int(con_notifytime * TICRATE); newline.PrintLevel = printlevel; if (AddType == NEWLINE || Text.Size() == 0) @@ -789,9 +789,6 @@ void FNotifyBuffer::AddString(int printlevel, FString source) AddType = NEWLINE; } - V_FreeBrokenLines (lines); - lines = NULL; - switch (source[source.Len()-1]) { case '\r': AddType = REPLACELINE; break; @@ -1185,22 +1182,22 @@ void C_DrawConsole () // No more enqueuing because adding new text to the console won't touch the actual print data. conbuffer->FormatText(ConFont, ConWidth / textScale); unsigned int consolelines = conbuffer->GetFormattedLineCount(); - FBrokenLines **blines = conbuffer->GetLines(); - FBrokenLines **printline = blines + consolelines - 1 - RowAdjust; + FBrokenLines *blines = conbuffer->GetLines(); + FBrokenLines *printline = blines + consolelines - 1 - RowAdjust; int bottomline = ConBottom / textScale - ConFont->GetHeight()*2 - 4; ConsoleDrawing = true; - for(FBrokenLines **p = printline; p >= blines && lines > 0; p--, lines--) + for(FBrokenLines *p = printline; p >= blines && lines > 0; p--, lines--) { if (textScale == 1) { - screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), (*p)->Text, TAG_DONE); + screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), p->Text, TAG_DONE); } else { - screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), (*p)->Text, + screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), p->Text, DTA_VirtualWidth, screen->GetWidth() / textScale, DTA_VirtualHeight, screen->GetHeight() / textScale, DTA_KeepRatio, true, TAG_DONE); diff --git a/src/c_consolebuffer.cpp b/src/c_consolebuffer.cpp index 0b501a774..9d32efffb 100644 --- a/src/c_consolebuffer.cpp +++ b/src/c_consolebuffer.cpp @@ -74,11 +74,11 @@ FConsoleBuffer::~FConsoleBuffer() void FConsoleBuffer::FreeBrokenText(unsigned start, unsigned end) { - if (end > mBrokenConsoleText.Size()) end = mBrokenConsoleText.Size(); + if (end > m_BrokenConsoleText.Size()) end = m_BrokenConsoleText.Size(); for (unsigned i = start; i < end; i++) { - if (mBrokenConsoleText[i] != NULL) V_FreeBrokenLines(mBrokenConsoleText[i]); - mBrokenConsoleText[i] = NULL; + m_BrokenConsoleText[i].Clear(); + m_BrokenConsoleText[i].ShrinkToFit(); } } @@ -260,7 +260,7 @@ void FConsoleBuffer::FormatText(FFont *formatfont, int displaywidth) if (formatfont != mLastFont || displaywidth != mLastDisplayWidth || mBufferWasCleared) { FreeBrokenText(); - mBrokenConsoleText.Clear(); + m_BrokenConsoleText.Clear(); mBrokenStart.Clear(); mBrokenStart.Push(0); mBrokenLines.Clear(); @@ -268,7 +268,7 @@ void FConsoleBuffer::FormatText(FFont *formatfont, int displaywidth) mLastDisplayWidth = displaywidth; mBufferWasCleared = false; } - unsigned brokensize = mBrokenConsoleText.Size(); + unsigned brokensize = m_BrokenConsoleText.Size(); if (brokensize == mConsoleText.Size()) { // The last line got text appended. We have to wait until here to format it because @@ -276,21 +276,19 @@ void FConsoleBuffer::FormatText(FFont *formatfont, int displaywidth) if (mLastLineNeedsUpdate) { brokensize--; - V_FreeBrokenLines(mBrokenConsoleText[brokensize]); - mBrokenConsoleText.Resize(brokensize); + m_BrokenConsoleText.Resize(brokensize); } } mBrokenLines.Resize(mBrokenStart[brokensize]); mBrokenStart.Resize(brokensize); for (unsigned i = brokensize; i < mConsoleText.Size(); i++) { - FBrokenLines *bl = V_BreakLines(formatfont, displaywidth, mConsoleText[i], true); - mBrokenConsoleText.Push(bl); + auto bl = V_BreakLines(formatfont, displaywidth, mConsoleText[i], true); + m_BrokenConsoleText.Push(bl); mBrokenStart.Push(mBrokenLines.Size()); - while (bl->Width != -1) + for(auto &bline : bl) { - mBrokenLines.Push(bl); - bl++; + mBrokenLines.Push(bline); } } mTextLines = mBrokenLines.Size(); diff --git a/src/c_consolebuffer.h b/src/c_consolebuffer.h index 710423012..1b46156b2 100644 --- a/src/c_consolebuffer.h +++ b/src/c_consolebuffer.h @@ -49,9 +49,9 @@ enum EAddType class FConsoleBuffer { TArray mConsoleText; - TArray mBrokenConsoleText; // This holds the structures returned by V_BreakLines and is used for memory management. + TArray> m_BrokenConsoleText; // This holds the structures returned by V_BreakLines and is used for memory management. TArray mBrokenStart; - TArray mBrokenLines; // This holds the single lines, indexed by mBrokenStart and is used for printing. + TArray mBrokenLines; // This holds the single lines, indexed by mBrokenStart and is used for printing. FILE * mLogFile; EAddType mAddType; int mTextLines; @@ -80,6 +80,6 @@ public: mConsoleText.Clear(); } int GetFormattedLineCount() { return mTextLines; } - FBrokenLines **GetLines() { return &mBrokenLines[0]; } + FBrokenLines *GetLines() { return &mBrokenLines[0]; } }; diff --git a/src/ctpl.h b/src/ctpl.h new file mode 100644 index 000000000..5956cf095 --- /dev/null +++ b/src/ctpl.h @@ -0,0 +1,251 @@ +/********************************************************* +* +* Copyright (C) 2014 by Vitaliy Vitsentiy +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*********************************************************/ + + +#ifndef __ctpl_stl_thread_pool_H__ +#define __ctpl_stl_thread_pool_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +// thread pool to run user's functors with signature +// ret func(int id, other_params) +// where id is the index of the thread that runs the functor +// ret is some return type + + +namespace ctpl { + + namespace detail { + template + class Queue { + public: + bool push(T const & value) { + std::unique_lock lock(this->mutex); + this->q.push(value); + return true; + } + // deletes the retrieved element, do not use for non integral types + bool pop(T & v) { + std::unique_lock lock(this->mutex); + if (this->q.empty()) + return false; + v = this->q.front(); + this->q.pop(); + return true; + } + bool empty() { + std::unique_lock lock(this->mutex); + return this->q.empty(); + } + private: + std::queue q; + std::mutex mutex; + }; + } + + class thread_pool { + + public: + + thread_pool() { this->init(); } + thread_pool(int nThreads) { this->init(); this->resize(nThreads); } + + // the destructor waits for all the functions in the queue to be finished + ~thread_pool() { + this->stop(true); + } + + // get the number of running threads in the pool + int size() { return static_cast(this->threads.size()); } + + // number of idle threads + int n_idle() { return this->nWaiting; } + std::thread & get_thread(int i) { return *this->threads[i]; } + + // change the number of threads in the pool + // should be called from one thread, otherwise be careful to not interleave, also with this->stop() + // nThreads must be >= 0 + void resize(int nThreads) { + if (!this->isStop && !this->isDone) { + int oldNThreads = static_cast(this->threads.size()); + if (oldNThreads <= nThreads) { // if the number of threads is increased + this->threads.resize(nThreads); + this->flags.resize(nThreads); + + for (int i = oldNThreads; i < nThreads; ++i) { + this->flags[i] = std::make_shared>(false); + this->set_thread(i); + } + } + else { // the number of threads is decreased + for (int i = oldNThreads - 1; i >= nThreads; --i) { + *this->flags[i] = true; // this thread will finish + this->threads[i]->detach(); + } + { + // stop the detached threads that were waiting + std::unique_lock lock(this->mutex); + this->cv.notify_all(); + } + this->threads.resize(nThreads); // safe to delete because the threads are detached + this->flags.resize(nThreads); // safe to delete because the threads have copies of shared_ptr of the flags, not originals + } + } + } + + // empty the queue + void clear_queue() { + std::function * _f; + while (this->q.pop(_f)) + delete _f; // empty the queue + } + + // pops a functional wrapper to the original function + std::function pop() { + std::function * _f = nullptr; + this->q.pop(_f); + std::unique_ptr> func(_f); // at return, delete the function even if an exception occurred + std::function f; + if (_f) + f = *_f; + return f; + } + + // wait for all computing threads to finish and stop all threads + // may be called asynchronously to not pause the calling thread while waiting + // if isWait == true, all the functions in the queue are run, otherwise the queue is cleared without running the functions + void stop(bool isWait = false) { + if (!isWait) { + if (this->isStop) + return; + this->isStop = true; + for (int i = 0, n = this->size(); i < n; ++i) { + *this->flags[i] = true; // command the threads to stop + } + this->clear_queue(); // empty the queue + } + else { + if (this->isDone || this->isStop) + return; + this->isDone = true; // give the waiting threads a command to finish + } + { + std::unique_lock lock(this->mutex); + this->cv.notify_all(); // stop all waiting threads + } + for (int i = 0; i < static_cast(this->threads.size()); ++i) { // wait for the computing threads to finish + if (this->threads[i]->joinable()) + this->threads[i]->join(); + } + // if there were no threads in the pool but some functors in the queue, the functors are not deleted by the threads + // therefore delete them here + this->clear_queue(); + this->threads.clear(); + this->flags.clear(); + } + + template + auto push(F && f, Rest&&... rest) ->std::future { + auto pck = std::make_shared>( + std::bind(std::forward(f), std::placeholders::_1, std::forward(rest)...) + ); + auto _f = new std::function([pck](int id) { + (*pck)(id); + }); + this->q.push(_f); + std::unique_lock lock(this->mutex); + this->cv.notify_one(); + return pck->get_future(); + } + + // run the user's function that excepts argument int - id of the running thread. returned value is templatized + // operator returns std::future, where the user can get the result and rethrow the catched exceptins + template + auto push(F && f) ->std::future { + auto pck = std::make_shared>(std::forward(f)); + auto _f = new std::function([pck](int id) { + (*pck)(id); + }); + this->q.push(_f); + std::unique_lock lock(this->mutex); + this->cv.notify_one(); + return pck->get_future(); + } + + + private: + + // deleted + thread_pool(const thread_pool &);// = delete; + thread_pool(thread_pool &&);// = delete; + thread_pool & operator=(const thread_pool &);// = delete; + thread_pool & operator=(thread_pool &&);// = delete; + + void set_thread(int i) { + std::shared_ptr> flag(this->flags[i]); // a copy of the shared ptr to the flag + auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() { + std::atomic & _flag = *flag; + std::function * _f; + bool isPop = this->q.pop(_f); + while (true) { + while (isPop) { // if there is anything in the queue + std::unique_ptr> func(_f); // at return, delete the function even if an exception occurred + (*_f)(i); + if (_flag) + return; // the thread is wanted to stop, return even if the queue is not empty yet + else + isPop = this->q.pop(_f); + } + // the queue is empty here, wait for the next command + std::unique_lock lock(this->mutex); + ++this->nWaiting; + this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; }); + --this->nWaiting; + if (!isPop) + return; // if the queue is empty and this->isDone == true or *flag then return + } + }; + this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique() + } + + void init() { this->nWaiting = 0; this->isStop = false; this->isDone = false; } + + std::vector> threads; + std::vector>> flags; + detail::Queue *> q; + std::atomic isDone; + std::atomic isStop; + std::atomic nWaiting; // how many threads are waiting + + std::mutex mutex; + std::condition_variable cv; + }; + +} + +#endif // __ctpl_stl_thread_pool_H__ diff --git a/src/d_netinf.h b/src/d_netinf.h index c5c82f65b..34df4bcea 100644 --- a/src/d_netinf.h +++ b/src/d_netinf.h @@ -36,8 +36,17 @@ #include "c_cvars.h" +enum +{ + GENDER_MALE, + GENDER_FEMALE, + GENDER_NEUTER, + GENDER_OBJECT, + GENDER_MAX +}; + int D_GenderToInt (const char *gender); -extern const char *GenderNames[3]; +extern const char *GenderNames[GENDER_MAX]; int D_PlayerClassToInt (const char *classname); diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index ef68ebf4e..b058d92d0 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -83,7 +83,7 @@ enum INFO_ClassicFlight, }; -const char *GenderNames[3] = { "male", "female", "other" }; +const char *GenderNames[GENDER_MAX] = { "male", "female", "neutral", "other" }; // Replace \ with %/ and % with %% FString D_EscapeUserInfo (const char *str) @@ -136,12 +136,14 @@ FString D_UnescapeUserInfo (const char *str, size_t len) int D_GenderToInt (const char *gender) { - if (!stricmp (gender, "female")) + if (gender[0] == 'f') return GENDER_FEMALE; - else if (!stricmp (gender, "other") || !stricmp (gender, "cyborg")) + else if (gender[0] == 'm') + return GENDER_MALE; + else if (gender[0] == 'n') return GENDER_NEUTER; else - return GENDER_MALE; + return GENDER_OBJECT; } int D_PlayerClassToInt (const char *classname) @@ -726,7 +728,8 @@ void D_WriteUserInfoStrings (int pnum, uint8_t **stream, bool compact) case NAME_Gender: *stream += sprintf(*((char **)stream), "\\%s", *static_cast(pair->Value) == GENDER_FEMALE ? "female" : - *static_cast(pair->Value) == GENDER_NEUTER ? "other" : "male"); + *static_cast(pair->Value) == GENDER_MALE ? "male" : + *static_cast(pair->Value) == GENDER_NEUTER ? "neutral" : "other"); break; case NAME_PlayerClass: diff --git a/src/d_player.h b/src/d_player.h index ee1418f57..2a8daa4f9 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -34,6 +34,8 @@ #include "a_weapons.h" +#include "d_netinf.h" + // The player data structure depends on a number // of other structs: items (internal inventory), // animation states (closely tied to the sprites @@ -261,13 +263,6 @@ public: extern TArray PlayerClasses; // User info (per-player copies of each CVAR_USERINFO cvar) -enum -{ - GENDER_MALE, - GENDER_FEMALE, - GENDER_NEUTER -}; - struct userinfo_t : TMap { ~userinfo_t(); diff --git a/src/doomdata.h b/src/doomdata.h index eb54f5f32..ee0b21f32 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -191,6 +191,8 @@ enum SPAC SPAC_MUse = 1<<8, // monsters can use SPAC_MPush = 1<<9, // monsters can push SPAC_UseBack = 1<<10, // Can be used from the backside + SPAC_Damage = 1<<11, // [ZZ] when linedef receives damage + SPAC_Death = 1<<12, // [ZZ] when linedef receives damage and has 0 health SPAC_PlayerActivate = (SPAC_Cross|SPAC_Use|SPAC_Impact|SPAC_Push|SPAC_AnyCross|SPAC_UseThrough|SPAC_UseBack), }; diff --git a/src/dthinker.cpp b/src/dthinker.cpp index 518bc464e..5605e6c53 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -254,14 +254,14 @@ DThinker::DThinker(no_link_type foo) throw() DThinker::~DThinker () { - assert(NextThinker == NULL && PrevThinker == NULL); + assert(NextThinker == nullptr && PrevThinker == nullptr); } void DThinker::OnDestroy () { - assert((NextThinker != NULL && PrevThinker != NULL) || - (NextThinker == NULL && PrevThinker == NULL)); - if (NextThinker != NULL) + assert((NextThinker != nullptr && PrevThinker != nullptr) || + (NextThinker == nullptr && PrevThinker == nullptr)); + if (NextThinker != nullptr) { Remove(); } @@ -282,7 +282,8 @@ void DThinker::Remove() } DThinker *prev = PrevThinker; DThinker *next = NextThinker; - assert(prev != NULL && next != NULL); + if (prev == nullptr && next == nullptr) return; // This was already removed earlier. + assert((ObjectFlags & OF_Sentinel) || (prev != this && next != this)); assert(prev->NextThinker == this); assert(next->PrevThinker == this); @@ -290,8 +291,8 @@ void DThinker::Remove() next->PrevThinker = prev; GC::WriteBarrier(prev, next); GC::WriteBarrier(next, prev); - NextThinker = NULL; - PrevThinker = NULL; + NextThinker = nullptr; + PrevThinker = nullptr; } //========================================================================== @@ -423,17 +424,22 @@ void DThinker::MarkRoots() void DThinker::DestroyAllThinkers () { int i; + bool error = false; for (i = 0; i <= MAX_STATNUM; i++) { if (i != STAT_TRAVELLING && i != STAT_STATIC) { - DestroyThinkersInList (Thinkers[i]); - DestroyThinkersInList (FreshThinkers[i]); + error |= DoDestroyThinkersInList (Thinkers[i]); + error |= DoDestroyThinkersInList (FreshThinkers[i]); } } - DestroyThinkersInList (Thinkers[MAX_STATNUM+1]); + error |= DoDestroyThinkersInList (Thinkers[MAX_STATNUM+1]); GC::FullGC(); + if (error) + { + I_Error("DestroyAllThinkers failed"); + } } //========================================================================== @@ -442,20 +448,70 @@ void DThinker::DestroyAllThinkers () // //========================================================================== -void DThinker::DestroyThinkersInList (FThinkerList &list) +void DThinker::DestroyThinkersInList(FThinkerList &list) { - if (list.Sentinel != NULL) + if (DoDestroyThinkersInList(list)) { - for (DThinker *node = list.Sentinel->NextThinker; node != list.Sentinel; node = list.Sentinel->NextThinker) - { - assert(node != NULL); - node->Destroy(); - } - list.Sentinel->Destroy(); - list.Sentinel = NULL; + I_Error("DestroyThinkersInList failed"); } } +//========================================================================== +// +// +// +//========================================================================== + +bool DThinker::DoDestroyThinkersInList (FThinkerList &list) +{ + bool error = false; + if (list.Sentinel != nullptr) + { + // Taking down the linked list live is far too dangerous in case something goes wrong. So first copy all elements into an array, take down the list and then destroy them. + + TArray toDelete; + DThinker *node = list.Sentinel->NextThinker; + while (node != list.Sentinel) + { + assert(node != nullptr); + auto next = node->NextThinker; + toDelete.Push(node); + node->NextThinker = node->PrevThinker = nullptr; // clear the links + node = next; + } + list.Sentinel->NextThinker = list.Sentinel->PrevThinker = nullptr; + list.Sentinel->Destroy(); + list.Sentinel = nullptr; + for(auto node : toDelete) + { + // We must intercept all exceptions so that we can continue deleting the list. + try + { + node->Destroy(); + } + catch (CVMAbortException &exception) + { + Printf("VM exception in DestroyThinkers:\n"); + exception.MaybePrintMessage(); + Printf("%s", exception.stacktrace.GetChars()); + // forcibly delete this. Cleanup may be incomplete, though. + node->ObjectFlags |= OF_YesReallyDelete; + delete node; + error = true; + } + catch (CRecoverableError &exception) + { + Printf("Error in DestroyThinkers: %s\n", exception.GetMessage()); + // forcibly delete this. Cleanup may be incomplete, though. + node->ObjectFlags |= OF_YesReallyDelete; + delete node; + error = true; + } + } + } + return error; +} + //========================================================================== // // diff --git a/src/dthinker.h b/src/dthinker.h index c9a2f7ba1..4ee5b3d80 100644 --- a/src/dthinker.h +++ b/src/dthinker.h @@ -96,6 +96,7 @@ public: DThinker(no_link_type) throw(); private: static void DestroyThinkersInList (FThinkerList &list); + static bool DoDestroyThinkersInList(FThinkerList &list); static int TickThinkers (FThinkerList *list, FThinkerList *dest); // Returns: # of thinkers ticked static int ProfileThinkers(FThinkerList *list, FThinkerList *dest); static void SaveList(FSerializer &arc, DThinker *node); diff --git a/src/events.cpp b/src/events.cpp index 1baa9b1d3..19034d495 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -301,7 +301,7 @@ void E_InitStaticHandlers(bool map) void E_Shutdown(bool map) { // delete handlers. - for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) { if (handler->IsStatic() == !map) handler->Destroy(); @@ -515,11 +515,18 @@ bool E_CheckReplacement( PClassActor *replacee, PClassActor **replacement ) return final; } -void E_NewGame() +void E_NewGame(EventHandlerType handlerType) { + bool isStatic = handlerType == EventHandlerType::Global; + + // Shut down all per-map event handlers before static NewGame events. + if (isStatic) + E_Shutdown(true); + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) { - handler->NewGame(); + if (handler->IsStatic() == isStatic) + handler->NewGame(); } } diff --git a/src/events.h b/src/events.h index 456db6c10..392ae0680 100755 --- a/src/events.h +++ b/src/events.h @@ -9,6 +9,12 @@ class DStaticEventHandler; +enum class EventHandlerType +{ + Global, + PerMap +}; + // register bool E_RegisterHandler(DStaticEventHandler* handler); // unregister @@ -73,7 +79,7 @@ void E_Console(int player, FString name, int arg1, int arg2, int arg3, bool manu bool E_CheckReplacement(PClassActor* replacee, PClassActor** replacement); // called on new game -void E_NewGame(); +void E_NewGame(EventHandlerType handlerType); // send networked event. unified function. bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3, bool manual); diff --git a/src/g_game.cpp b/src/g_game.cpp index f5d9b5d98..ffc5b40b5 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1054,7 +1054,7 @@ void G_Ticker () switch (gameaction) { case ga_loadlevel: - G_DoLoadLevel (-1, false); + G_DoLoadLevel (-1, false, false); break; case ga_recordgame: G_CheckDemoStatus(); @@ -2986,6 +2986,7 @@ DEFINE_GLOBAL(playeringame) DEFINE_GLOBAL(PlayerClasses) DEFINE_GLOBAL_NAMED(Skins, PlayerSkins) DEFINE_GLOBAL(consoleplayer) +DEFINE_GLOBAL_NAMED(PClass::AllClasses, AllClasses) DEFINE_GLOBAL_NAMED(PClassActor::AllActorClasses, AllActorClasses) DEFINE_GLOBAL(validcount) DEFINE_GLOBAL(multiplayer) diff --git a/src/g_level.cpp b/src/g_level.cpp index 36843919b..05c41614a 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -526,11 +526,8 @@ void G_InitNew (const char *mapname, bool bTitleLevel) { gamestate = GS_LEVEL; } - G_DoLoadLevel (0, false); - if(!savegamerestore) - { - E_NewGame(); - } + + G_DoLoadLevel (0, false, !savegamerestore); } // @@ -744,7 +741,7 @@ void G_DoCompleted (void) if (gamestate == GS_TITLELEVEL) { level.MapName = nextlevel; - G_DoLoadLevel (startpos, false); + G_DoLoadLevel (startpos, false, false); startpos = 0; viewactive = true; return; @@ -919,7 +916,7 @@ void DAutosaver::Tick () extern gamestate_t wipegamestate; -void G_DoLoadLevel (int position, bool autosave) +void G_DoLoadLevel (int position, bool autosave, bool newGame) { static int lastposition = 0; gamestate_t oldgs = gamestate; @@ -1005,7 +1002,12 @@ void G_DoLoadLevel (int position, bool autosave) level.maptime = 0; - P_SetupLevel (level.MapName, position); + if (newGame) + { + E_NewGame(EventHandlerType::Global); + } + + P_SetupLevel (level.MapName, position, newGame); AM_LevelInit(); @@ -1253,7 +1255,7 @@ void G_DoWorldDone (void) level.MapName = nextlevel; } G_StartTravel (); - G_DoLoadLevel (startpos, true); + G_DoLoadLevel (startpos, true, false); startpos = 0; gameaction = ga_nothing; viewactive = true; diff --git a/src/g_level.h b/src/g_level.h index 98bdb4229..a98a8d415 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -507,7 +507,7 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill void G_StartTravel (); int G_FinishTravel (); -void G_DoLoadLevel (int position, bool autosave); +void G_DoLoadLevel (int position, bool autosave, bool newGame); void G_InitLevelLocals (void); diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 28d0c16df..dfdb6dbef 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -40,6 +40,8 @@ #include "r_defs.h" #include "portal.h" #include "p_blockmap.h" +#include "p_local.h" +#include "p_destructible.h" struct FLevelLocals { @@ -96,6 +98,9 @@ struct FLevelLocals TArray Zones; + // [ZZ] Destructible geometry information + TMap healthGroups; + FBlockmap blockmap; // These are copies of the loaded map data that get used by the savegame code to skip unaltered fields diff --git a/src/g_shared/hudmessages.cpp b/src/g_shared/hudmessages.cpp index 14c6cd53a..3e0db7874 100644 --- a/src/g_shared/hudmessages.cpp +++ b/src/g_shared/hudmessages.cpp @@ -194,7 +194,6 @@ DHUDMessage::DHUDMessage (FFont *font, const char *text, float x, float y, int h WrapWidth = 0; HandleAspect = true; Top = y; - Lines = NULL; HoldTics = (int)(holdTime * TICRATE); Tics = 0; TextColor = textColor; @@ -215,11 +214,6 @@ DHUDMessage::DHUDMessage (FFont *font, const char *text, float x, float y, int h void DHUDMessage::OnDestroy() { - if (Lines) - { - V_FreeBrokenLines (Lines); - Lines = NULL; - } if (SourceText != NULL) { delete[] SourceText; @@ -260,7 +254,6 @@ void DHUDMessage::Serialize(FSerializer &arc) if (arc.isReading()) { - Lines = NULL; ResetText(SourceText); } } @@ -329,24 +322,16 @@ void DHUDMessage::ResetText (const char *text) width = SCREENWIDTH / active_con_scaletext(); } - if (Lines != NULL) - { - V_FreeBrokenLines (Lines); - } - Lines = V_BreakLines (Font, NoWrap ? INT_MAX : width, (uint8_t *)text); - NumLines = 0; + NumLines = Lines.Size(); Width = 0; Height = 0; - if (Lines) + for (auto &line : Lines) { - for (; Lines[NumLines].Width >= 0; NumLines++) - { - Height += Font->GetHeight (); - Width = MAX (Width, Lines[NumLines].Width); - } + Height += Font->GetHeight (); + Width = MAX (Width, line.Width); } } diff --git a/src/g_statusbar/sbar.h b/src/g_statusbar/sbar.h index 968c91760..9f7bbb4cc 100644 --- a/src/g_statusbar/sbar.h +++ b/src/g_statusbar/sbar.h @@ -133,7 +133,7 @@ public: } protected: - FBrokenLines *Lines; + TArray Lines; int Width, Height, NumLines; float Left, Top; bool CenterX, NoWrap; diff --git a/src/g_statusbar/sbarinfo_commands.cpp b/src/g_statusbar/sbarinfo_commands.cpp index 15b0663af..0eba791b3 100644 --- a/src/g_statusbar/sbarinfo_commands.cpp +++ b/src/g_statusbar/sbarinfo_commands.cpp @@ -686,12 +686,11 @@ class CommandDrawString : public SBarInfoCommand { if(lineBreaks) { - FBrokenLines *lines = V_BreakLines(font, breakWidth, str.GetChars()); - for(int i = 0;lines[i].Width >= 0;i++) + auto lines = V_BreakLines(font, breakWidth, str.GetChars()); + for(unsigned i = 0; i < lines.Size();i++) { statusBar->DrawString(font, lines[i].Text, x, y+i*(font->GetHeight()+4), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translation, spacing, shadow, shadowX, shadowY); } - V_FreeBrokenLines(lines); } else statusBar->DrawString(font, str.GetChars(), x, y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translation, spacing, shadow, shadowX, shadowY); diff --git a/src/g_statusbar/shared_sbar.cpp b/src/g_statusbar/shared_sbar.cpp index 6be4abf9f..1aaa6a18d 100644 --- a/src/g_statusbar/shared_sbar.cpp +++ b/src/g_statusbar/shared_sbar.cpp @@ -588,6 +588,13 @@ DEFINE_ACTION_FUNCTION(DBaseStatusBar, BeginHUD) return 0; } +DEFINE_ACTION_FUNCTION(DBaseStatusBar, UpdateScreenGeometry) +{ + PARAM_SELF_PROLOGUE(DBaseStatusBar); + setsizeneeded = true; + return 0; +} + //--------------------------------------------------------------------------- // // PROC AttachToPlayer @@ -1099,10 +1106,10 @@ void DBaseStatusBar::DrawLog () hudheight = SCREENHEIGHT / scale; int linelen = hudwidth<640? Scale(hudwidth,9,10)-40 : 560; - FBrokenLines *lines = V_BreakLines (SmallFont, linelen, CPlayer->LogText); + auto lines = V_BreakLines (SmallFont, linelen, CPlayer->LogText); int height = 20; - for (int i = 0; lines[i].Width != -1; i++) height += SmallFont->GetHeight () + 1; + for (unsigned i = 0; i < lines.Size(); i++) height += SmallFont->GetHeight () + 1; int x,y,w; @@ -1123,16 +1130,13 @@ void DBaseStatusBar::DrawLog () Scale(w, SCREENWIDTH, hudwidth), Scale(height, SCREENHEIGHT, hudheight)); x+=20; y+=10; - for (int i = 0; lines[i].Width != -1; i++) + for (const FBrokenLines &line : lines) { - - screen->DrawText (SmallFont, CR_UNTRANSLATED, x, y, lines[i].Text, + screen->DrawText (SmallFont, CR_UNTRANSLATED, x, y, line.Text, DTA_KeepRatio, true, DTA_VirtualWidth, hudwidth, DTA_VirtualHeight, hudheight, TAG_DONE); y += SmallFont->GetHeight ()+1; } - - V_FreeBrokenLines (lines); } } @@ -1875,13 +1879,12 @@ DEFINE_ACTION_FUNCTION(DBaseStatusBar, DrawString) if (wrapwidth > 0) { - FBrokenLines *brk = V_BreakLines(font->mFont, wrapwidth, string, true); - for (int i = 0; brk[i].Width >= 0; i++) + auto brk = V_BreakLines(font->mFont, wrapwidth, string, true); + for (auto &line : brk) { - self->DrawString(font->mFont, brk[i].Text, x, y, flags, alpha, trans, font->mSpacing, font->mMonospaced, font->mShadowX, font->mShadowY); + self->DrawString(font->mFont, line.Text, x, y, flags, alpha, trans, font->mSpacing, font->mMonospaced, font->mShadowX, font->mShadowY); y += font->mFont->GetHeight() + linespacing; } - V_FreeBrokenLines(brk); } else { diff --git a/src/gl/data/gl_uniformbuffer.cpp b/src/gl/data/gl_uniformbuffer.cpp deleted file mode 100644 index 010c0aff1..000000000 --- a/src/gl/data/gl_uniformbuffer.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2018 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_uniformbuffer.cpp -** Uniform buffer abstraction class. -** -**/ - - -#include "gl_load/gl_system.h" -#include "gl_load/gl_interface.h" -#include "gl_uniformbuffer.h" - - -//========================================================================== -// -// -// -//========================================================================== - -GLUniformBuffer::GLUniformBuffer(size_t size, bool staticdraw) -: IUniformBuffer(size), mStaticDraw(staticdraw) -{ - glGenBuffers(1, &mBufferId); -} - -//========================================================================== -// -// -// -//========================================================================== - -GLUniformBuffer::~GLUniformBuffer() -{ - if (mBufferId != 0) - { - glDeleteBuffers(1, &mBufferId); - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void GLUniformBuffer::SetData(const void *data) -{ - if (mBufferId != 0) - { - glBindBuffer(GL_UNIFORM_BUFFER, mBufferId); - glBufferData(GL_UNIFORM_BUFFER, mSize, data, mStaticDraw? GL_STATIC_DRAW : GL_STREAM_DRAW); - } -} - -//========================================================================== -// -// This needs to go away later. -// -//========================================================================== - -void GLUniformBuffer::Bind(int bindingpoint) -{ - glBindBufferBase(GL_UNIFORM_BUFFER, bindingpoint, mBufferId); -} diff --git a/src/gl/data/gl_uniformbuffer.h b/src/gl/data/gl_uniformbuffer.h deleted file mode 100644 index 64c1831be..000000000 --- a/src/gl/data/gl_uniformbuffer.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include "hwrenderer/data/uniformbuffer.h" - - -class GLUniformBuffer : public IUniformBuffer -{ - unsigned mBufferId; - bool mStaticDraw; - -public: - GLUniformBuffer(size_t size, bool staticdraw = false); - ~GLUniformBuffer(); - - void SetData(const void *data) override; - void Bind(int bindingpoint) override; - - unsigned ID() const - { - return mBufferId; - } - -}; diff --git a/src/gl/data/gl_vertexbuffer.cpp b/src/gl/data/gl_vertexbuffer.cpp deleted file mode 100644 index f9fe4a667..000000000 --- a/src/gl/data/gl_vertexbuffer.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2005-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_vertexbuffer.cpp -** Vertex buffer handling. -** -**/ - -#include "gl_load/gl_system.h" -#include "doomtype.h" -#include "p_local.h" -#include "r_state.h" -#include "gl_load/gl_interface.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/shaders/gl_shader.h" -#include "gl/data/gl_vertexbuffer.h" - - -//========================================================================== -// -// Create / destroy the VBO -// -//========================================================================== - -FVertexBuffer::FVertexBuffer(bool wantbuffer) -{ - vbo_id = 0; - if (wantbuffer) glGenBuffers(1, &vbo_id); -} - -FVertexBuffer::~FVertexBuffer() -{ - if (vbo_id != 0) - { - glDeleteBuffers(1, &vbo_id); - } -} - - -void FSimpleVertexBuffer::BindVBO() -{ - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glVertexAttribPointer(VATTR_VERTEX, 3, GL_FLOAT, false, sizeof(FSimpleVertex), &VSiO->x); - glVertexAttribPointer(VATTR_TEXCOORD, 2, GL_FLOAT, false, sizeof(FSimpleVertex), &VSiO->u); - glVertexAttribPointer(VATTR_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof(FSimpleVertex), &VSiO->color); - glEnableVertexAttribArray(VATTR_VERTEX); - glEnableVertexAttribArray(VATTR_TEXCOORD); - glEnableVertexAttribArray(VATTR_COLOR); - glDisableVertexAttribArray(VATTR_VERTEX2); - glDisableVertexAttribArray(VATTR_NORMAL); -} - -void FSimpleVertexBuffer::EnableColorArray(bool on) -{ - if (on) - { - glEnableVertexAttribArray(VATTR_COLOR); - } - else - { - glDisableVertexAttribArray(VATTR_COLOR); - } -} - - -void FSimpleVertexBuffer::set(FSimpleVertex *verts, int count) -{ - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - gl_RenderState.ResetVertexBuffer(); - gl_RenderState.SetVertexBuffer(this); - glBufferData(GL_ARRAY_BUFFER, count * sizeof(*verts), verts, GL_STREAM_DRAW); -} - -//========================================================================== -// -// -// -//========================================================================== - -FFlatVertexBuffer::FFlatVertexBuffer(int width, int height) -: FVertexBuffer(true), FFlatVertexGenerator(width, height) -{ - ibo_id = 0; - glGenBuffers(1, &ibo_id); - if (gl.buffermethod == BM_PERSISTENT) - { - unsigned int bytesize = BUFFER_SIZE * sizeof(FFlatVertex); - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glBufferStorage(GL_ARRAY_BUFFER, bytesize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - map = (FFlatVertex*)glMapBufferRange(GL_ARRAY_BUFFER, 0, bytesize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - DPrintf(DMSG_NOTIFY, "Using persistent buffer\n"); - } - else - { - unsigned int bytesize = BUFFER_SIZE * sizeof(FFlatVertex); - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glBufferData(GL_ARRAY_BUFFER, bytesize, NULL, GL_STREAM_DRAW); - map = nullptr; - DPrintf(DMSG_NOTIFY, "Using deferred buffer\n"); - } - mIndex = mCurIndex = 0; - mNumReserved = NUM_RESERVED; - - mMap = map; - Map(); - memcpy(map, &vbo_shadowdata[0], mNumReserved * sizeof(FFlatVertex)); - Unmap(); -} - -FFlatVertexBuffer::~FFlatVertexBuffer() -{ - if (vbo_id != 0) - { - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glUnmapBuffer(GL_ARRAY_BUFFER); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - if (ibo_id != 0) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(1, &ibo_id); - } - map = nullptr; -} - -void FFlatVertexBuffer::OutputResized(int width, int height) -{ - FFlatVertexGenerator::OutputResized(width, height); - Map(); - memcpy(&map[4], &vbo_shadowdata[4], 4 * sizeof(FFlatVertex)); - Unmap(); -} - -void FFlatVertexBuffer::BindVBO() -{ - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - glVertexAttribPointer(VATTR_VERTEX, 3, GL_FLOAT, false, sizeof(FFlatVertex), &VTO->x); - glVertexAttribPointer(VATTR_TEXCOORD, 2, GL_FLOAT, false, sizeof(FFlatVertex), &VTO->u); - glEnableVertexAttribArray(VATTR_VERTEX); - glEnableVertexAttribArray(VATTR_TEXCOORD); - glDisableVertexAttribArray(VATTR_COLOR); - glDisableVertexAttribArray(VATTR_VERTEX2); - glDisableVertexAttribArray(VATTR_NORMAL); -} - -void FFlatVertexBuffer::Map() -{ - if (gl.buffermethod == BM_DEFERRED) - { - unsigned int bytesize = BUFFER_SIZE * sizeof(FFlatVertex); - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - gl_RenderState.ResetVertexBuffer(); - mMap = map = (FFlatVertex*)glMapBufferRange(GL_ARRAY_BUFFER, 0, bytesize, GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); - } -} - -void FFlatVertexBuffer::Unmap() -{ - if (gl.buffermethod == BM_DEFERRED) - { - unsigned int bytesize = BUFFER_SIZE * sizeof(FFlatVertex); - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - gl_RenderState.ResetVertexBuffer(); - glUnmapBuffer(GL_ARRAY_BUFFER); - mMap = map = nullptr; - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void FFlatVertexBuffer::CreateVBO() -{ - vbo_shadowdata.Resize(mNumReserved); - FFlatVertexGenerator::CreateVertices(); - mCurIndex = mIndex = vbo_shadowdata.Size(); - Map(); - memcpy(map, &vbo_shadowdata[0], vbo_shadowdata.Size() * sizeof(FFlatVertex)); - Unmap(); - if (ibo_id > 0) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, ibo_data.Size() * sizeof(uint32_t), &ibo_data[0], GL_STATIC_DRAW); - } -} diff --git a/src/gl/data/gl_vertexbuffer.h b/src/gl/data/gl_vertexbuffer.h deleted file mode 100644 index 7c54b1916..000000000 --- a/src/gl/data/gl_vertexbuffer.h +++ /dev/null @@ -1,230 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2005-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// - -#ifndef __VERTEXBUFFER_H -#define __VERTEXBUFFER_H - -#include -#include -#include -#include "tarray.h" -#include "hwrenderer/utility/hw_clock.h" -#include "gl_load/gl_interface.h" -#include "r_data/models/models.h" -#include "hwrenderer/data/flatvertices.h" -#include "hwrenderer/scene/hw_skydome.h" - -struct vertex_t; -struct secplane_t; -struct subsector_t; -struct sector_t; -class FMaterial; - -enum -{ - VATTR_VERTEX_BIT, - VATTR_TEXCOORD_BIT, - VATTR_COLOR_BIT, - VATTR_VERTEX2_BIT, - VATTR_NORMAL_BIT -}; - - -class FVertexBuffer -{ -protected: - unsigned int vbo_id; - -public: - FVertexBuffer(bool wantbuffer = true); - virtual ~FVertexBuffer(); - virtual void BindVBO() = 0; - void EnableBufferArrays(int enable, int disable); -}; - -struct FSimpleVertex -{ - float x, z, y; // world position - float u, v; // texture coordinates - PalEntry color; - - void Set(float xx, float zz, float yy, float uu = 0, float vv = 0, PalEntry col = 0xffffffff) - { - x = xx; - z = zz; - y = yy; - u = uu; - v = vv; - color = col; - } -}; - -#define VTO ((FFlatVertex*)NULL) -#define VSiO ((FSimpleVertex*)NULL) - -class FSimpleVertexBuffer : public FVertexBuffer -{ - TArray mBuffer; -public: - FSimpleVertexBuffer() - { - } - void BindVBO(); - void set(FSimpleVertex *verts, int count); - void EnableColorArray(bool on); -}; - -class FFlatVertexBuffer : public FVertexBuffer, public FFlatVertexGenerator -{ - unsigned int ibo_id; - FFlatVertex *map; - unsigned int mIndex; - std::atomic mCurIndex; - std::mutex mBufferMutex; - unsigned int mNumReserved; - - - static const unsigned int BUFFER_SIZE = 2000000; - static const unsigned int BUFFER_SIZE_TO_USE = 1999500; - -public: - enum - { - QUAD_INDEX = 0, - FULLSCREEN_INDEX = 4, - PRESENT_INDEX = 8, - STENCILTOP_INDEX = 12, - STENCILBOTTOM_INDEX = 16, - - NUM_RESERVED = 20 - }; - - FFlatVertexBuffer(int width, int height); - ~FFlatVertexBuffer(); - - void OutputResized(int width, int height); - - void BindVBO(); - - void CreateVBO(); - - FFlatVertex *GetBuffer() - { - return &map[mCurIndex]; - } - - template - FFlatVertex *Alloc(int num, T *poffset) - { - again: - FFlatVertex *p = GetBuffer(); - auto index = mCurIndex.fetch_add(num); - *poffset = static_cast(index); - if (index + num >= BUFFER_SIZE_TO_USE) - { - std::lock_guard lock(mBufferMutex); - if (mCurIndex >= BUFFER_SIZE_TO_USE) // retest condition, in case another thread got here first - mCurIndex = mIndex; - - if (index >= BUFFER_SIZE_TO_USE) goto again; - } - return p; - } - - unsigned int GetCount(FFlatVertex *newptr, unsigned int *poffset) - { - unsigned int newofs = (unsigned int)(newptr - map); - unsigned int diff = newofs - mCurIndex; - *poffset = mCurIndex; - mCurIndex = newofs; - if (mCurIndex >= BUFFER_SIZE_TO_USE) mCurIndex = mIndex; - return diff; - } -#ifdef __GL_PCH_H // we need the system includes for this but we cannot include them ourselves without creating #define clashes. The affected files wouldn't try to draw anyway. - void RenderArray(unsigned int primtype, unsigned int offset, unsigned int count) - { - drawcalls.Clock(); - glDrawArrays(primtype, offset, count); - drawcalls.Unclock(); - } - - void RenderCurrent(FFlatVertex *newptr, unsigned int primtype, unsigned int *poffset = NULL, unsigned int *pcount = NULL) - { - unsigned int offset; - unsigned int count = GetCount(newptr, &offset); - RenderArray(primtype, offset, count); - if (poffset) *poffset = offset; - if (pcount) *pcount = count; - } - -#endif - - uint32_t *GetIndexPointer() const - { - return ibo_id == 0 ? &ibo_data[0] : nullptr; - } - - void Reset() - { - mCurIndex = mIndex; - } - - void Map(); - void Unmap(); -}; - - -class FSkyVertexBuffer : public FVertexBuffer, public FSkyDomeCreator -{ - void RenderRow(int prim, int row); - -public: - - FSkyVertexBuffer(); - void RenderDome(FMaterial *tex, int mode); - void BindVBO(); -}; - -class FModelVertexBuffer : public FVertexBuffer, public IModelVertexBuffer -{ - int mIndexFrame[2]; - FModelVertex *vbo_ptr; - uint32_t ibo_id; - -public: - - FModelVertexBuffer(bool needindex, bool singleframe); - ~FModelVertexBuffer(); - - FModelVertex *LockVertexBuffer(unsigned int size) override; - void UnlockVertexBuffer() override; - - unsigned int *LockIndexBuffer(unsigned int size) override; - void UnlockIndexBuffer() override; - - void SetupFrame(FModelRenderer *renderer, unsigned int frame1, unsigned int frame2, unsigned int size) override; - void BindVBO() override; -}; - -#define VSO ((FSkyVertex*)NULL) - -#endif \ No newline at end of file diff --git a/src/gl/data/gl_viewpointbuffer.cpp b/src/gl/data/gl_viewpointbuffer.cpp deleted file mode 100644 index 611db9e40..000000000 --- a/src/gl/data/gl_viewpointbuffer.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2018 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_viewpointbuffer.cpp -** Buffer data maintenance for per viewpoint uniform data -** -**/ - -#include "gl_load/gl_system.h" -#include "gl_load/gl_interface.h" -#include "hwrenderer/data/shaderuniforms.h" -#include "hwrenderer/scene/hw_viewpointuniforms.h" -#include "gl_viewpointbuffer.h" - -static const int INITIAL_BUFFER_SIZE = 100; // 100 viewpoints per frame should nearly always be enough - -GLViewpointBuffer::GLViewpointBuffer() -{ - mBufferSize = INITIAL_BUFFER_SIZE; - mBlockAlign = ((sizeof(HWViewpointUniforms) / gl.uniformblockalignment) + 1) * gl.uniformblockalignment; - mByteSize = mBufferSize * mBlockAlign; - Allocate(); - Clear(); - mLastMappedIndex = UINT_MAX; - mClipPlaneInfo.Push(0); -} - -GLViewpointBuffer::~GLViewpointBuffer() -{ - glBindBuffer(GL_UNIFORM_BUFFER, 0); - glDeleteBuffers(1, &mBufferId); -} - -void GLViewpointBuffer::Allocate() -{ - glGenBuffers(1, &mBufferId); - glBindBufferBase(GL_UNIFORM_BUFFER, VIEWPOINT_BINDINGPOINT, mBufferId); - glBindBuffer(GL_UNIFORM_BUFFER, mBufferId); // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should. - if (gl.flags & RFL_BUFFER_STORAGE) - { - glBufferStorage(GL_UNIFORM_BUFFER, mByteSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); - mBufferPointer = glMapBufferRange(GL_UNIFORM_BUFFER, 0, mByteSize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); - } - else - { - glBufferData(GL_UNIFORM_BUFFER, mByteSize, NULL, GL_STATIC_DRAW); - mBufferPointer = NULL; - } -} - -void GLViewpointBuffer::CheckSize() -{ - if (mUploadIndex >= mBufferSize) - { - // reallocate the buffer with twice the size - unsigned int oldbuffer = mBufferId; - - mBufferSize *= 2; - mByteSize *= 2; - - // first unmap the old buffer - glBindBuffer(GL_UNIFORM_BUFFER, mBufferId); - glUnmapBuffer(GL_UNIFORM_BUFFER); - - Allocate(); - glBindBuffer(GL_COPY_READ_BUFFER, oldbuffer); - - // copy contents and delete the old buffer. - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_UNIFORM_BUFFER, 0, 0, mByteSize / 2); // old size is half of the current one. - glBindBuffer(GL_COPY_READ_BUFFER, 0); - glDeleteBuffers(1, &oldbuffer); - } -} - -void GLViewpointBuffer::Map() -{ - if (!(gl.flags & RFL_BUFFER_STORAGE)) - { - glBindBuffer(GL_UNIFORM_BUFFER, mBufferId); - mBufferPointer = (float*)glMapBufferRange(GL_UNIFORM_BUFFER, 0, mByteSize, GL_MAP_WRITE_BIT); - } -} - -void GLViewpointBuffer::Unmap() -{ - if (!(gl.flags & RFL_BUFFER_STORAGE)) - { - glBindBuffer(GL_UNIFORM_BUFFER, mBufferId); - glUnmapBuffer(GL_UNIFORM_BUFFER); - mBufferPointer = nullptr; - } - else - { - glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); - } -} - -int GLViewpointBuffer::Bind(unsigned int index) -{ - if (index != mLastMappedIndex) - { - mLastMappedIndex = index; - glBindBufferRange(GL_UNIFORM_BUFFER, VIEWPOINT_BINDINGPOINT, mBufferId, index * mBlockAlign, mBlockAlign); - - // Update the viewpoint-related clip plane setting. - if (!(gl.flags & RFL_NO_CLIP_PLANES)) - { - if (mClipPlaneInfo[index]) - { - glEnable(GL_CLIP_DISTANCE0); - } - else - { - glDisable(GL_CLIP_DISTANCE0); - } - } - } - return index; -} - -void GLViewpointBuffer::Set2D(int width, int height) -{ - if (width != m2DWidth || height != m2DHeight) - { - HWViewpointUniforms matrices; - matrices.SetDefaults(); - matrices.mProjectionMatrix.ortho(0, width, height, 0, -1.0f, 1.0f); - matrices.CalcDependencies(); - Map(); - memcpy(mBufferPointer, &matrices, sizeof(matrices)); - Unmap(); - m2DWidth = width; - m2DHeight = height; - mLastMappedIndex = -1; - } - Bind(0); -} - -int GLViewpointBuffer::SetViewpoint(HWViewpointUniforms *vp) -{ - CheckSize(); - Map(); - memcpy(((char*)mBufferPointer) + mUploadIndex * mBlockAlign, vp, sizeof(*vp)); - Unmap(); - - mClipPlaneInfo.Push(vp->mClipHeightDirection != 0.f || vp->mClipLine.X > -10000000.0f); - return Bind(mUploadIndex++); -} - -void GLViewpointBuffer::Clear() -{ - // Index 0 is reserved for the 2D projection. - mUploadIndex = 1; - mClipPlaneInfo.Resize(1); -} - diff --git a/src/gl/dynlights/gl_lightbuffer.cpp b/src/gl/dynlights/gl_lightbuffer.cpp deleted file mode 100644 index 465189937..000000000 --- a/src/gl/dynlights/gl_lightbuffer.cpp +++ /dev/null @@ -1,213 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2014-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_lightbuffer.cpp -** Buffer data maintenance for dynamic lights -** -**/ - -#include "gl_load/gl_system.h" -#include "gl/shaders/gl_shader.h" -#include "gl/dynlights/gl_lightbuffer.h" -#include "hwrenderer/utility/hw_clock.h" -#include "hwrenderer/dynlights/hw_dynlightdata.h" -#include "hwrenderer/data/shaderuniforms.h" - -static const int INITIAL_BUFFER_SIZE = 160000; // This means 80000 lights per frame and 160000*16 bytes == 2.56 MB. - -FLightBuffer::FLightBuffer() -{ - - mBufferSize = INITIAL_BUFFER_SIZE; - mByteSize = mBufferSize * sizeof(float); - // Hack alert: On Intel's GL driver SSBO's perform quite worse than UBOs. - // We only want to disable using SSBOs for lights but not disable the feature entirely. - // Note that using an uniform buffer here will limit the number of lights per surface so it isn't done for NVidia and AMD. - if (gl.flags & RFL_SHADER_STORAGE_BUFFER && !strstr(gl.vendorstring, "Intel")) - { - mBufferType = GL_SHADER_STORAGE_BUFFER; - mBlockAlign = 0; - mBlockSize = mBufferSize; - } - else - { - mBufferType = GL_UNIFORM_BUFFER; - mBlockSize = gl.maxuniformblock / 16; - if (mBlockSize > 2048) mBlockSize = 2048; // we don't really need a larger buffer - - mBlockAlign = mBlockSize / 2; - } - - glGenBuffers(1, &mBufferId); - glBindBufferBase(mBufferType, LIGHTBUF_BINDINGPOINT, mBufferId); - glBindBuffer(mBufferType, mBufferId); // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should. - if (gl.lightmethod == LM_DIRECT) - { - glBufferStorage(mBufferType, mByteSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - } - else - { - glBufferData(mBufferType, mByteSize, NULL, GL_DYNAMIC_DRAW); - mBufferPointer = NULL; - } - - Clear(); - mLastMappedIndex = UINT_MAX; -} - -FLightBuffer::~FLightBuffer() -{ - glBindBuffer(mBufferType, 0); - glDeleteBuffers(1, &mBufferId); -} - -void FLightBuffer::Clear() -{ - mIndex = 0; - mIndices.Clear(); - mUploadIndex = 0; -} - -int FLightBuffer::UploadLights(FDynLightData &data) -{ - int size0 = data.arrays[0].Size()/4; - int size1 = data.arrays[1].Size()/4; - int size2 = data.arrays[2].Size()/4; - int totalsize = size0 + size1 + size2 + 1; - - // pointless type casting because some compilers can't print enough warnings. - if (mBlockAlign > 0 && (unsigned int)totalsize + (mIndex % mBlockAlign) > mBlockSize) - { - mIndex = ((mIndex + mBlockAlign) / mBlockAlign) * mBlockAlign; - - // can't be rendered all at once. - if ((unsigned int)totalsize > mBlockSize) - { - int diff = totalsize - (int)mBlockSize; - - size2 -= diff; - if (size2 < 0) - { - size1 += size2; - size2 = 0; - } - if (size1 < 0) - { - size0 += size1; - size1 = 0; - } - totalsize = size0 + size1 + size2 + 1; - } - } - - if (totalsize <= 1) return -1; - - if (mIndex + totalsize > mBufferSize/4) - { - // reallocate the buffer with twice the size - unsigned int newbuffer; - - // first unmap the old buffer - glBindBuffer(mBufferType, mBufferId); - glUnmapBuffer(mBufferType); - - // create and bind the new buffer, bind the old one to a copy target (too bad that DSA is not yet supported well enough to omit this crap.) - glGenBuffers(1, &newbuffer); - glBindBufferBase(mBufferType, LIGHTBUF_BINDINGPOINT, newbuffer); - glBindBuffer(mBufferType, newbuffer); // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should. - glBindBuffer(GL_COPY_READ_BUFFER, mBufferId); - - // create the new buffer's storage (twice as large as the old one) - mBufferSize *= 2; - mByteSize *= 2; - if (gl.lightmethod == LM_DIRECT) - { - glBufferStorage(mBufferType, mByteSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - } - else - { - glBufferData(mBufferType, mByteSize, NULL, GL_DYNAMIC_DRAW); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT); - } - - // copy contents and delete the old buffer. - glCopyBufferSubData(GL_COPY_READ_BUFFER, mBufferType, 0, 0, mByteSize/2); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - glDeleteBuffers(1, &mBufferId); - mBufferId = newbuffer; - } - - float *copyptr; - - assert(mBufferPointer != NULL); - if (mBufferPointer == NULL) return -1; - copyptr = mBufferPointer + mIndex * 4; - - float parmcnt[] = { 0, float(size0), float(size0 + size1), float(size0 + size1 + size2) }; - - memcpy(©ptr[0], parmcnt, 4 * sizeof(float)); - memcpy(©ptr[4], &data.arrays[0][0], 4 * size0*sizeof(float)); - memcpy(©ptr[4 + 4*size0], &data.arrays[1][0], 4 * size1*sizeof(float)); - memcpy(©ptr[4 + 4*(size0 + size1)], &data.arrays[2][0], 4 * size2*sizeof(float)); - - unsigned int bufferindex = mIndex; - mIndex += totalsize; - draw_dlight += (totalsize-1) / 2; - return bufferindex; -} - -void FLightBuffer::Begin() -{ - if (gl.lightmethod == LM_DEFERRED) - { - glBindBuffer(mBufferType, mBufferId); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT); - } -} - -void FLightBuffer::Finish() -{ - if (gl.lightmethod == LM_DEFERRED) - { - glBindBuffer(mBufferType, mBufferId); - glUnmapBuffer(mBufferType); - mBufferPointer = NULL; - } -} - -int FLightBuffer::BindUBO(unsigned int index) -{ - unsigned int offset = (index / mBlockAlign) * mBlockAlign; - - if (offset != mLastMappedIndex) - { - // this will only get called if a uniform buffer is used. For a shader storage buffer we only need to bind the buffer once at the start to all shader programs - mLastMappedIndex = offset; - glBindBufferRange(GL_UNIFORM_BUFFER, LIGHTBUF_BINDINGPOINT, mBufferId, offset*16, mBlockSize*16); // we go from counting vec4's to counting bytes here. - } - return (index - offset); -} - - - diff --git a/src/gl/dynlights/gl_lightbuffer.h b/src/gl/dynlights/gl_lightbuffer.h deleted file mode 100644 index 959dddcbd..000000000 --- a/src/gl/dynlights/gl_lightbuffer.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef __GL_LIGHTBUFFER_H -#define __GL_LIGHTBUFFER_H - -#include "tarray.h" -#include "hwrenderer/dynlights/hw_dynlightdata.h" - -class FLightBuffer -{ - TArray mIndices; - unsigned int mBufferId; - float * mBufferPointer; - - unsigned int mBufferType; - unsigned int mIndex; - unsigned int mUploadIndex; - unsigned int mLastMappedIndex; - unsigned int mBlockAlign; - unsigned int mBlockSize; - unsigned int mBufferSize; - unsigned int mByteSize; - -public: - - FLightBuffer(); - ~FLightBuffer(); - void Clear(); - int UploadLights(FDynLightData &data); - void Begin(); - void Finish(); - int BindUBO(unsigned int index); - unsigned int GetBlockSize() const { return mBlockSize; } - unsigned int GetBufferType() const { return mBufferType; } - unsigned int GetIndexPtr() const { return mIndices.Size(); } - void StoreIndex(int index) { mIndices.Push(index); } - int GetIndex(int i) const { return mIndices[i]; } -}; - -int gl_SetDynModelLight(AActor *self, int dynlightindex); - -#endif - diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp deleted file mode 100644 index 57346b263..000000000 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// -//--------------------------------------------------------------------------- -// 1D dynamic shadow maps (OpenGL dependent part) -// Copyright(C) 2017 Magnus Norddahl -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// - -#include "gl_load/gl_system.h" -#include "gl/shaders/gl_shader.h" -#include "gl/dynlights/gl_shadowmap.h" -#include "gl/system/gl_debug.h" -#include "hwrenderer/utility/hw_cvars.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_postprocessstate.h" -#include "gl/renderer/gl_renderbuffers.h" -#include "hwrenderer/postprocessing/hw_shadowmapshader.h" -#include "hwrenderer/dynlights/hw_dynlightdata.h" -#include "stats.h" - -void FShadowMap::Update() -{ - UpdateCycles.Reset(); - LightsProcessed = 0; - LightsShadowmapped = 0; - - if (!IsEnabled()) - return; - - UpdateCycles.Clock(); - - UploadAABBTree(); - UploadLights(); - - FGLDebug::PushGroup("ShadowMap"); - FGLPostProcessState savedState; - - GLRenderer->mBuffers->BindShadowMapFB(); - - GLRenderer->mShadowMapShader->Bind(NOQUEUE); - GLRenderer->mShadowMapShader->Uniforms->ShadowmapQuality = gl_shadowmap_quality; - GLRenderer->mShadowMapShader->Uniforms.Set(); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, mLightList); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mNodesBuffer); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLinesBuffer); - - glViewport(0, 0, gl_shadowmap_quality, 1024); - GLRenderer->RenderScreenQuad(); - - const auto &viewport = screen->mScreenViewport; - glViewport(viewport.left, viewport.top, viewport.width, viewport.height); - - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); - - GLRenderer->mBuffers->BindShadowMapTexture(16); - - FGLDebug::PopGroup(); - - UpdateCycles.Unclock(); -} - -void FShadowMap::UploadLights() -{ - CollectLights(); - - if (mLightList == 0) - glGenBuffers(1, (GLuint*)&mLightList); - - int oldBinding = 0; - glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, mLightList); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * mLights.Size(), &mLights[0], GL_STATIC_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); -} - -void FShadowMap::UploadAABBTree() -{ - if (!ValidateAABBTree()) - { - int oldBinding = 0; - glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); - - glGenBuffers(1, (GLuint*)&mNodesBuffer); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, mNodesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(hwrenderer::AABBTreeNode) * mAABBTree->nodes.Size(), &mAABBTree->nodes[0], GL_STATIC_DRAW); - - glGenBuffers(1, (GLuint*)&mLinesBuffer); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, mLinesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(hwrenderer::AABBTreeLine) * mAABBTree->lines.Size(), &mAABBTree->lines[0], GL_STATIC_DRAW); - - glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); - } -} - -void FShadowMap::Clear() -{ - if (mLightList != 0) - { - glDeleteBuffers(1, (GLuint*)&mLightList); - mLightList = 0; - } - - if (mNodesBuffer != 0) - { - glDeleteBuffers(1, (GLuint*)&mNodesBuffer); - mNodesBuffer = 0; - } - - if (mLinesBuffer != 0) - { - glDeleteBuffers(1, (GLuint*)&mLinesBuffer); - mLinesBuffer = 0; - } -} diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h deleted file mode 100644 index ee43abef8..000000000 --- a/src/gl/dynlights/gl_shadowmap.h +++ /dev/null @@ -1,30 +0,0 @@ - -#pragma once - -#include "hwrenderer/dynlights/hw_shadowmap.h" - -class FShadowMap : public IShadowMap -{ -public: - ~FShadowMap() { Clear(); } - - // Release resources - void Clear() override; - - // Update shadow map texture - void Update() override; - -private: - // Upload the AABB-tree to the GPU - void UploadAABBTree(); - - // Upload light list to the GPU - void UploadLights(); - - // OpenGL storage buffer with the list of lights in the shadow map texture - int mLightList = 0; - - // OpenGL storage buffers for the AABB tree - int mNodesBuffer = 0; - int mLinesBuffer = 0; -}; diff --git a/src/gl/models/gl_models.cpp b/src/gl/models/gl_models.cpp deleted file mode 100644 index 97c5c09fd..000000000 --- a/src/gl/models/gl_models.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2005-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_models.cpp -** -** OpenGL renderer model handling code -** -**/ - -#include "gl_load/gl_system.h" -#include "w_wad.h" -#include "g_game.h" -#include "doomstat.h" -#include "g_level.h" -#include "r_state.h" -#include "d_player.h" -#include "g_levellocals.h" -#include "i_time.h" -#include "hwrenderer/textures/hw_material.h" - -#include "gl_load/gl_interface.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/scene/gl_drawinfo.h" -#include "gl/scene/gl_portal.h" -#include "gl/models/gl_models.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/shaders/gl_shader.h" - -CVAR(Bool, gl_light_models, true, CVAR_ARCHIVE) - -VSMatrix FGLModelRenderer::GetViewToWorldMatrix() -{ - VSMatrix objectToWorldMatrix; - di->VPUniforms.mViewMatrix.inverseMatrix(objectToWorldMatrix); - return objectToWorldMatrix; -} - -void FGLModelRenderer::BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) -{ - glDepthFunc(GL_LEQUAL); - gl_RenderState.EnableTexture(true); - // [BB] In case the model should be rendered translucent, do back face culling. - // This solves a few of the problems caused by the lack of depth sorting. - // [Nash] Don't do back face culling if explicitly specified in MODELDEF - // TO-DO: Implement proper depth sorting. - if (!(actor->RenderStyle == LegacyRenderStyles[STYLE_Normal]) && !(smf->flags & MDL_DONTCULLBACKFACES)) - { - glEnable(GL_CULL_FACE); - glFrontFace((mirrored ^ GLRenderer->mPortalState.isMirrored()) ? GL_CCW : GL_CW); - } - - gl_RenderState.mModelMatrix = objectToWorldMatrix; - gl_RenderState.EnableModelMatrix(true); -} - -void FGLModelRenderer::EndDrawModel(AActor *actor, FSpriteModelFrame *smf) -{ - gl_RenderState.EnableModelMatrix(false); - - glDepthFunc(GL_LESS); - if (!(actor->RenderStyle == LegacyRenderStyles[STYLE_Normal]) && !(smf->flags & MDL_DONTCULLBACKFACES)) - glDisable(GL_CULL_FACE); -} - -void FGLModelRenderer::BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) -{ - glDepthFunc(GL_LEQUAL); - - // [BB] In case the model should be rendered translucent, do back face culling. - // This solves a few of the problems caused by the lack of depth sorting. - // TO-DO: Implement proper depth sorting. - if (!(actor->RenderStyle == LegacyRenderStyles[STYLE_Normal])) - { - glEnable(GL_CULL_FACE); - glFrontFace((mirrored ^ GLRenderer->mPortalState.isMirrored()) ? GL_CW : GL_CCW); - } - - gl_RenderState.mModelMatrix = objectToWorldMatrix; - gl_RenderState.EnableModelMatrix(true); -} - -void FGLModelRenderer::EndDrawHUDModel(AActor *actor) -{ - gl_RenderState.EnableModelMatrix(false); - - glDepthFunc(GL_LESS); - if (!(actor->RenderStyle == LegacyRenderStyles[STYLE_Normal])) - glDisable(GL_CULL_FACE); -} - -IModelVertexBuffer *FGLModelRenderer::CreateVertexBuffer(bool needindex, bool singleframe) -{ - return new FModelVertexBuffer(needindex, singleframe); -} - -void FGLModelRenderer::SetVertexBuffer(IModelVertexBuffer *buffer) -{ - gl_RenderState.SetVertexBuffer((FModelVertexBuffer*)buffer); -} - -void FGLModelRenderer::ResetVertexBuffer() -{ - gl_RenderState.SetVertexBuffer(GLRenderer->mVBO); -} - -void FGLModelRenderer::SetInterpolation(double inter) -{ - gl_RenderState.SetInterpolationFactor((float)inter); -} - -void FGLModelRenderer::SetMaterial(FTexture *skin, bool clampNoFilter, int translation) -{ - FMaterial * tex = FMaterial::ValidateTexture(skin, false); - gl_RenderState.SetMaterial(tex, clampNoFilter ? CLAMP_NOFILTER : CLAMP_NONE, translation, -1, false); - - gl_RenderState.Apply(); - if (modellightindex != -1) gl_RenderState.ApplyLightIndex(modellightindex); -} - -void FGLModelRenderer::DrawArrays(int start, int count) -{ - glDrawArrays(GL_TRIANGLES, start, count); -} - -void FGLModelRenderer::DrawElements(int numIndices, size_t offset) -{ - glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, (void*)(intptr_t)offset); -} - -//=========================================================================== -// -// Uses a hardware buffer if either single frame (i.e. no interpolation needed) -// or shading is available (interpolation is done by the vertex shader) -// -// If interpolation has to be done on the CPU side this will fall back -// to CPU-side arrays. -// -//=========================================================================== - -FModelVertexBuffer::FModelVertexBuffer(bool needindex, bool singleframe) - : FVertexBuffer(true) -{ - vbo_ptr = nullptr; - ibo_id = 0; - if (needindex) - { - glGenBuffers(1, &ibo_id); // The index buffer can always be a real buffer. - } -} - -//=========================================================================== -// -// -// -//=========================================================================== - -void FModelVertexBuffer::BindVBO() -{ - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glEnableVertexAttribArray(VATTR_VERTEX); - glEnableVertexAttribArray(VATTR_TEXCOORD); - glEnableVertexAttribArray(VATTR_VERTEX2); - glEnableVertexAttribArray(VATTR_NORMAL); - glDisableVertexAttribArray(VATTR_COLOR); -} - -//=========================================================================== -// -// -// -//=========================================================================== - -FModelVertexBuffer::~FModelVertexBuffer() -{ - if (ibo_id != 0) - { - glDeleteBuffers(1, &ibo_id); - } - if (vbo_ptr != nullptr) - { - delete[] vbo_ptr; - } -} - -//=========================================================================== -// -// -// -//=========================================================================== - -FModelVertex *FModelVertexBuffer::LockVertexBuffer(unsigned int size) -{ - if (vbo_id > 0) - { - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glBufferData(GL_ARRAY_BUFFER, size * sizeof(FModelVertex), nullptr, GL_STATIC_DRAW); - return (FModelVertex*)glMapBufferRange(GL_ARRAY_BUFFER, 0, size * sizeof(FModelVertex), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); - } - else - { - if (vbo_ptr != nullptr) delete[] vbo_ptr; - vbo_ptr = new FModelVertex[size]; - memset(vbo_ptr, 0, size * sizeof(FModelVertex)); - return vbo_ptr; - } -} - -//=========================================================================== -// -// -// -//=========================================================================== - -void FModelVertexBuffer::UnlockVertexBuffer() -{ - if (vbo_id > 0) - { - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glUnmapBuffer(GL_ARRAY_BUFFER); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== - -unsigned int *FModelVertexBuffer::LockIndexBuffer(unsigned int size) -{ - if (ibo_id != 0) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(unsigned int), NULL, GL_STATIC_DRAW); - return (unsigned int*)glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0, size * sizeof(unsigned int), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); - } - else - { - return nullptr; - } -} - -//=========================================================================== -// -// -// -//=========================================================================== - -void FModelVertexBuffer::UnlockIndexBuffer() -{ - if (ibo_id > 0) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); - } -} - - -//=========================================================================== -// -// Sets up the buffer starts for frame interpolation -// This must be called after gl_RenderState.Apply! -// -//=========================================================================== -static TArray iBuffer; - -void FModelVertexBuffer::SetupFrame(FModelRenderer *renderer, unsigned int frame1, unsigned int frame2, unsigned int size) -{ - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - if (vbo_id > 0) - { - glVertexAttribPointer(VATTR_VERTEX, 3, GL_FLOAT, false, sizeof(FModelVertex), &VMO[frame1].x); - glVertexAttribPointer(VATTR_TEXCOORD, 2, GL_FLOAT, false, sizeof(FModelVertex), &VMO[frame1].u); - glVertexAttribPointer(VATTR_VERTEX2, 3, GL_FLOAT, false, sizeof(FModelVertex), &VMO[frame2].x); - glVertexAttribPointer(VATTR_NORMAL, 4, GL_INT_2_10_10_10_REV, true, sizeof(FModelVertex), &VMO[frame2].packedNormal); - } - else if (frame1 == frame2 || size == 0 || gl_RenderState.GetInterpolationFactor() == 0.f) - { - glVertexPointer(3, GL_FLOAT, sizeof(FModelVertex), &vbo_ptr[frame1].x); - glTexCoordPointer(2, GL_FLOAT, sizeof(FModelVertex), &vbo_ptr[frame1].u); - } - else - { - // must interpolate - iBuffer.Resize(size); - glVertexPointer(3, GL_FLOAT, sizeof(FModelVertex), &iBuffer[0].x); - glTexCoordPointer(2, GL_FLOAT, sizeof(FModelVertex), &vbo_ptr[frame1].u); - float frac = gl_RenderState.GetInterpolationFactor(); - for (unsigned i = 0; i < size; i++) - { - iBuffer[i].x = vbo_ptr[frame1 + i].x * (1.f - frac) + vbo_ptr[frame2 + i].x * frac; - iBuffer[i].y = vbo_ptr[frame1 + i].y * (1.f - frac) + vbo_ptr[frame2 + i].y * frac; - iBuffer[i].z = vbo_ptr[frame1 + i].z * (1.f - frac) + vbo_ptr[frame2 + i].z * frac; - } - } -} diff --git a/src/gl/renderer/gl_lightdata.cpp b/src/gl/renderer/gl_lightdata.cpp deleted file mode 100644 index 4668af261..000000000 --- a/src/gl/renderer/gl_lightdata.cpp +++ /dev/null @@ -1,213 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2002-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_light.cpp -** Light level / fog management / dynamic lights -** -**/ - -#include "gl_load/gl_system.h" -#include "gl_load/gl_interface.h" -#include "hwrenderer/utility/hw_cvars.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/shaders/gl_shader.h" -#include "gl/scene/gl_portal.h" -#include "p_local.h" -#include "r_sky.h" - - - - -//========================================================================== -// -// Sets render state to draw the given render style -// includes: Texture mode, blend equation and blend mode -// -//========================================================================== - -void gl_GetRenderStyle(FRenderStyle style, bool drawopaque, bool allowcolorblending, - int *tm, int *sb, int *db, int *be) -{ - static int blendstyles[] = { GL_ZERO, GL_ONE, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, }; - static int renderops[] = { 0, GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1 }; - - int srcblend = blendstyles[style.SrcAlpha%STYLEALPHA_MAX]; - int dstblend = blendstyles[style.DestAlpha%STYLEALPHA_MAX]; - int blendequation = renderops[style.BlendOp&15]; - int texturemode = drawopaque? TM_OPAQUE : TM_MODULATE; - - if (style.Flags & STYLEF_RedIsAlpha) - { - texturemode = TM_REDTOALPHA; - } - else if (style.Flags & STYLEF_ColorIsFixed) - { - texturemode = TM_MASK; - } - else if (style.Flags & STYLEF_InvertSource) - { - texturemode = TM_INVERSE; - } - - if (blendequation == -1) - { - srcblend = GL_DST_COLOR; - dstblend = GL_ONE_MINUS_SRC_ALPHA; - blendequation = GL_FUNC_ADD; - } - - if (allowcolorblending && srcblend == GL_SRC_ALPHA && dstblend == GL_ONE && blendequation == GL_FUNC_ADD) - { - srcblend = GL_SRC_COLOR; - } - - *tm = texturemode; - *be = blendequation; - *sb = srcblend; - *db = dstblend; -} - - -//========================================================================== -// -// set current light color -// -//========================================================================== -void gl_SetColor(int sectorlightlevel, int rellight, bool fullbright, const FColormap &cm, float alpha, bool weapon) -{ - if (fullbright) - { - gl_RenderState.SetColorAlpha(0xffffff, alpha, 0); - gl_RenderState.SetSoftLightLevel(255); - } - else - { - int hwlightlevel = hw_CalcLightLevel(sectorlightlevel, rellight, weapon, cm.BlendFactor); - PalEntry pe = hw_CalcLightColor(hwlightlevel, cm.LightColor, cm.BlendFactor); - gl_RenderState.SetColorAlpha(pe, alpha, cm.Desaturation); - gl_RenderState.SetSoftLightLevel(hw_ClampLight(sectorlightlevel + rellight), cm.BlendFactor); - } -} - -//========================================================================== -// -// Lighting stuff -// -//========================================================================== - -void gl_SetShaderLight(float level, float olight) -{ - const float MAXDIST = 256.f; - const float THRESHOLD = 96.f; - const float FACTOR = 0.75f; - - if (level > 0) - { - float lightdist, lightfactor; - - if (olight < THRESHOLD) - { - lightdist = (MAXDIST/2) + (olight * MAXDIST / THRESHOLD / 2); - olight = THRESHOLD; - } - else lightdist = MAXDIST; - - lightfactor = 1.f + ((olight/level) - 1.f) * FACTOR; - if (lightfactor == 1.f) lightdist = 0.f; // save some code in the shader - gl_RenderState.SetLightParms(lightfactor, lightdist); - } - else - { - gl_RenderState.SetLightParms(1.f, 0.f); - } -} - - -//========================================================================== -// -// Sets the fog for the current polygon -// -//========================================================================== - -void gl_SetFog(int lightlevel, int rellight, bool fullbright, const FColormap *cmap, bool isadditive) -{ - PalEntry fogcolor; - float fogdensity; - - if (level.flags&LEVEL_HASFADETABLE) - { - fogdensity=70; - fogcolor=0x808080; - } - else if (cmap != NULL && !fullbright) - { - fogcolor = cmap->FadeColor; - fogdensity = hw_GetFogDensity(lightlevel, fogcolor, cmap->FogDensity, cmap->BlendFactor); - fogcolor.a=0; - } - else - { - fogcolor = 0; - fogdensity = 0; - } - - // Make fog a little denser when inside a skybox - if (GLRenderer->mPortalState.inskybox) fogdensity+=fogdensity/2; - - - // no fog in enhanced vision modes! - if (fogdensity==0 || gl_fogmode == 0) - { - gl_RenderState.EnableFog(false); - gl_RenderState.SetFog(0,0); - } - else - { - if ((level.lightmode == 2 || (level.lightmode == 8 && cmap->BlendFactor > 0)) && fogcolor == 0) - { - float light = hw_CalcLightLevel(lightlevel, rellight, false, cmap->BlendFactor); - gl_SetShaderLight(light, lightlevel); - } - else - { - gl_RenderState.SetLightParms(1.f, 0.f); - } - - // For additive rendering using the regular fog color here would mean applying it twice - // so always use black - if (isadditive) - { - fogcolor=0; - } - - gl_RenderState.EnableFog(true); - gl_RenderState.SetFog(fogcolor, fogdensity); - - // Korshun: fullbright fog like in software renderer. - if (level.lightmode == 8 && cmap->BlendFactor == 0 && level.brightfog && fogdensity != 0 && fogcolor != 0) - { - gl_RenderState.SetSoftLightLevel(255); - } - } -} diff --git a/src/gl/renderer/gl_lightdata.h b/src/gl/renderer/gl_lightdata.h deleted file mode 100644 index 8fcd9809f..000000000 --- a/src/gl/renderer/gl_lightdata.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __GL_LIGHTDATA -#define __GL_LIGHTDATA - -#include "v_palette.h" -#include "r_defs.h" -#include "r_data/renderstyle.h" -#include "hwrenderer/utility/hw_lighting.h" -#include "r_data/colormaps.h" - -void gl_GetRenderStyle(FRenderStyle style, bool drawopaque, bool allowcolorblending, - int *tm, int *sb, int *db, int *be); - -void gl_SetColor(int light, int rellight, bool fullbright, const FColormap &cm, float alpha, bool weapon=false); -void gl_SetFog(int lightlevel, int rellight, bool fullbright, const FColormap *cm, bool isadditive); - - - -#endif diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index d327e9eb3..d08443c57 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -30,19 +30,19 @@ #include "m_png.h" #include "r_utility.h" #include "d_player.h" +#include "gl/system/gl_buffers.h" #include "gl/system/gl_framebuffer.h" #include "hwrenderer/utility/hw_cvars.h" #include "gl/system/gl_debug.h" -#include "gl/renderer/gl_lightdata.h" #include "gl/renderer/gl_renderstate.h" #include "gl/renderer/gl_renderbuffers.h" #include "gl/renderer/gl_renderer.h" #include "gl/renderer/gl_postprocessstate.h" -#include "gl/data/gl_vertexbuffer.h" #include "hwrenderer/postprocessing/hw_presentshader.h" #include "hwrenderer/postprocessing/hw_postprocess.h" #include "hwrenderer/postprocessing/hw_postprocess_cvars.h" #include "hwrenderer/utility/hw_vrmodes.h" +#include "hwrenderer/data/flatvertices.h" #include "gl/shaders/gl_postprocessshaderinstance.h" #include "gl/textures/gl_hwtexture.h" #include "r_videoscale.h" @@ -51,11 +51,15 @@ extern bool vid_hdr_active; CVAR(Int, gl_dither_bpc, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +namespace OpenGLRenderer +{ + + void FGLRenderer::RenderScreenQuad() { - mVBO->BindVBO(); - gl_RenderState.ResetVertexBuffer(); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_STRIP, FFlatVertexBuffer::PRESENT_INDEX, 4); + auto buffer = static_cast(screen->mVertexData->GetBufferObjects().first); + buffer->Bind(nullptr); + glDrawArrays(GL_TRIANGLE_STRIP, FFlatVertexBuffer::PRESENT_INDEX, 4); } void FGLRenderer::PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D) @@ -158,7 +162,7 @@ void FGLRenderer::Flush() FGLDebug::PushGroup("PresentEyes"); // Note: This here is the ONLY place in the entire engine where the OpenGL dependent parts of the Stereo3D code need to be dealt with. // There's absolutely no need to create a overly complex class hierarchy for just this. - GLRenderer->PresentStereo(); + PresentStereo(); FGLDebug::PopGroup(); } } @@ -201,7 +205,7 @@ void FGLRenderer::DrawPresentTexture(const IntRect &box, bool applyGamma) { glViewport(box.left, box.top, box.width, box.height); - GLRenderer->mBuffers->BindDitherTexture(1); + mBuffers->BindDitherTexture(1); glActiveTexture(GL_TEXTURE0); if (ViewportLinearScale()) @@ -288,3 +292,4 @@ void FGLRenderer::ClearBorders() glDisable(GL_SCISSOR_TEST); } +} \ No newline at end of file diff --git a/src/gl/renderer/gl_postprocessstate.cpp b/src/gl/renderer/gl_postprocessstate.cpp index 1edcc949b..e74041f9e 100644 --- a/src/gl/renderer/gl_postprocessstate.cpp +++ b/src/gl/renderer/gl_postprocessstate.cpp @@ -28,9 +28,11 @@ #include "templates.h" #include "gl_load/gl_system.h" #include "gl_load/gl_interface.h" -#include "gl/data/gl_vertexbuffer.h" #include "gl/renderer/gl_postprocessstate.h" +namespace OpenGLRenderer +{ + //----------------------------------------------------------------------------- // // Saves state modified by post processing shaders @@ -134,3 +136,5 @@ FGLPostProcessState::~FGLPostProcessState() glActiveTexture(activeTex); } + +} \ No newline at end of file diff --git a/src/gl/renderer/gl_postprocessstate.h b/src/gl/renderer/gl_postprocessstate.h index 6f2b39a03..7d41f6571 100644 --- a/src/gl/renderer/gl_postprocessstate.h +++ b/src/gl/renderer/gl_postprocessstate.h @@ -7,6 +7,9 @@ #include "c_cvars.h" #include "r_defs.h" +namespace OpenGLRenderer +{ + class FGLPostProcessState { public: @@ -35,4 +38,5 @@ private: GLint blendDestAlpha; }; +} #endif diff --git a/src/gl/renderer/gl_quaddrawer.cpp b/src/gl/renderer/gl_quaddrawer.cpp deleted file mode 100644 index fe0d3813d..000000000 --- a/src/gl/renderer/gl_quaddrawer.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// - -#include "gl_load/gl_system.h" -#include "gl/shaders/gl_shader.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_quaddrawer.h" - -/* -** For handling of dynamically created quads when no persistently mapped -** buffer or client array is available (i.e. GL 3.x core profiles) -** -** In this situation the 4 vertices of a quad primitive are being passed -** as a matrix uniform because that is a lot faster than any kind of -** temporary buffer change. -*/ - -FFlatVertex FQuadDrawer::buffer[4]; - -//========================================================================== -// -// -// -//========================================================================== - -void FQuadDrawer::DoRender(int type) -{ - // When this gets called, the render state must already be applied so we can just - // send our vertices to the current shader. - float matV[16], matT[16]; - - for(int i=0;i<4;i++) - { - matV[i*4+0] = buffer[i].x; - matV[i*4+1] = buffer[i].z; - matV[i*4+2] = buffer[i].y; - matV[i*4+3] = 1; - matT[i*4+0] = buffer[i].u; - matT[i*4+1] = buffer[i].v; - matT[i*4+2] = matT[i*4+3] = 0; - } - FShader *shader = GLRenderer->mShaderManager->GetActiveShader(); - glUniformMatrix4fv(shader->vertexmatrix_index, 1, false, matV); - glUniformMatrix4fv(shader->texcoordmatrix_index, 1, false, matT); - glUniform1i(shader->quadmode_index, 1); - GLRenderer->mVBO->RenderArray(type, FFlatVertexBuffer::QUAD_INDEX, 4); - glUniform1i(shader->quadmode_index, 0); -} diff --git a/src/gl/renderer/gl_quaddrawer.h b/src/gl/renderer/gl_quaddrawer.h deleted file mode 100644 index c4776ecae..000000000 --- a/src/gl/renderer/gl_quaddrawer.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef __QDRAWER_H -#define __QDRAWER_H - -#include "gl/data/gl_vertexbuffer.h" - -class FQuadDrawer -{ - FFlatVertex *p; - int ndx; - static FFlatVertex buffer[4]; - - void DoRender(int type); -public: - - FQuadDrawer() - { - if (gl.buffermethod == BM_DEFERRED) - { - p = buffer; - } - else - { - p = GLRenderer->mVBO->Alloc(4, &ndx); - } - } - FFlatVertex *Pointer() - { - return p; - } - void Set(int ndx, float x, float y, float z, float s, float t) - { - p[ndx].Set(x, y, z, s, t); - } - void Render(int type) - { - if (gl.buffermethod == BM_DEFERRED) - { - DoRender(type); - } - else - { - GLRenderer->mVBO->RenderArray(type, ndx, 4); - } - } -}; - - -#endif diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index 48cbd0e75..d5c78f116 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -38,6 +38,9 @@ CVAR(Int, gl_multisample, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +namespace OpenGLRenderer +{ + //========================================================================== // // Initialize render buffers and textures used in rendering passes @@ -966,9 +969,9 @@ void FGLRenderBuffers::RenderEffect(const FString &name) if (step.Uniforms.Size > 0) { if (!shader->Uniforms) - shader->Uniforms.reset(screen->CreateUniformBuffer(step.Uniforms.Size)); - shader->Uniforms->SetData(step.Uniforms.Data); - shader->Uniforms->Bind(POSTPROCESS_BINDINGPOINT); + shader->Uniforms.reset(screen->CreateDataBuffer(POSTPROCESS_BINDINGPOINT, false)); + shader->Uniforms->SetData(step.Uniforms.Size, step.Uniforms.Data); + shader->Uniforms->BindBase(); } // Set shader @@ -986,3 +989,5 @@ void FGLRenderBuffers::RenderEffect(const FString &name) FGLDebug::PopGroup(); } + +} \ No newline at end of file diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h index a6b148803..10c4e37ed 100644 --- a/src/gl/renderer/gl_renderbuffers.h +++ b/src/gl/renderer/gl_renderbuffers.h @@ -4,6 +4,9 @@ #include "gl/shaders/gl_shader.h" #include "hwrenderer/postprocessing/hw_postprocess.h" +namespace OpenGLRenderer +{ + class PPGLTexture { public: @@ -168,3 +171,5 @@ private: static bool FailedCreate; }; + +} \ No newline at end of file diff --git a/src/gl/renderer/gl_renderer.cpp b/src/gl/renderer/gl_renderer.cpp index d4b6197f9..ef1e6221d 100644 --- a/src/gl/renderer/gl_renderer.cpp +++ b/src/gl/renderer/gl_renderer.cpp @@ -35,6 +35,7 @@ #include "p_effect.h" #include "d_player.h" #include "a_dynlight.h" +#include "cmdlib.h" #include "g_game.h" #include "swrenderer/r_swscene.h" #include "hwrenderer/utility/hw_clock.h" @@ -42,29 +43,32 @@ #include "gl_load/gl_interface.h" #include "gl/system/gl_framebuffer.h" #include "hwrenderer/utility/hw_cvars.h" -#include "gl/scene/gl_portal.h" #include "gl/system/gl_debug.h" #include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_lightdata.h" #include "gl/renderer/gl_renderstate.h" #include "gl/renderer/gl_renderbuffers.h" -#include "gl/data/gl_vertexbuffer.h" -#include "gl/scene/gl_drawinfo.h" #include "hwrenderer/utility/hw_vrmodes.h" #include "hwrenderer/postprocessing/hw_presentshader.h" #include "hwrenderer/postprocessing/hw_present3dRowshader.h" #include "hwrenderer/postprocessing/hw_shadowmapshader.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/scene/hw_skydome.h" #include "gl/shaders/gl_postprocessshaderinstance.h" #include "gl/textures/gl_samplers.h" -#include "gl/dynlights/gl_lightbuffer.h" -#include "gl/data/gl_viewpointbuffer.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hwrenderer/data/hw_viewpointbuffer.h" #include "r_videoscale.h" +#include "r_data/models/models.h" +#include "gl/renderer/gl_postprocessstate.h" EXTERN_CVAR(Int, screenblocks) EXTERN_CVAR(Bool, cl_capfps) extern bool NoInterpolateView; +namespace OpenGLRenderer +{ + //=========================================================================== // // Renderer interface @@ -101,15 +105,9 @@ void FGLRenderer::Initialize(int width, int height) glBindVertexArray(mVAOID); FGLDebug::LabelObject(GL_VERTEX_ARRAY, mVAOID, "FGLRenderer.mVAOID"); - mVBO = new FFlatVertexBuffer(width, height); - mSkyVBO = new FSkyVertexBuffer; - mLights = new FLightBuffer(); - mViewpoints = new GLViewpointBuffer; - gl_RenderState.SetVertexBuffer(mVBO); mFBID = 0; mOldFBID = 0; - SetupLevel(); mShaderManager = new FShaderManager; mSamplerManager = new FSamplerManager; } @@ -121,10 +119,6 @@ FGLRenderer::~FGLRenderer() FMaterial::FlushAll(); if (mShaderManager != nullptr) delete mShaderManager; if (mSamplerManager != nullptr) delete mSamplerManager; - if (mVBO != nullptr) delete mVBO; - if (mSkyVBO != nullptr) delete mSkyVBO; - if (mLights != nullptr) delete mLights; - if (mViewpoints != nullptr) delete mViewpoints; if (mFBID != 0) glDeleteFramebuffers(1, &mFBID); if (mVAOID != 0) { @@ -157,11 +151,6 @@ void FGLRenderer::ResetSWScene() swdrawer = nullptr; } -void FGLRenderer::SetupLevel() -{ - mVBO->CreateVBO(); -} - //=========================================================================== // // @@ -191,6 +180,38 @@ void FGLRenderer::EndOffscreen() glBindFramebuffer(GL_FRAMEBUFFER, mOldFBID); } +//=========================================================================== +// +// +// +//=========================================================================== + +void FGLRenderer::UpdateShadowMap() +{ + if (screen->mShadowMap.PerformUpdate()) + { + FGLDebug::PushGroup("ShadowMap"); + + FGLPostProcessState savedState; + + mBuffers->BindShadowMapFB(); + + mShadowMapShader->Bind(NOQUEUE); + mShadowMapShader->Uniforms->ShadowmapQuality = gl_shadowmap_quality; + mShadowMapShader->Uniforms.Set(); + + glViewport(0, 0, gl_shadowmap_quality, 1024); + RenderScreenQuad(); + + const auto &viewport = screen->mScreenViewport; + glViewport(viewport.left, viewport.top, viewport.width, viewport.height); + + mBuffers->BindShadowMapTexture(16); + FGLDebug::PopGroup(); + screen->mShadowMap.FinishUpdate(); + } +} + //----------------------------------------------------------------------------- // // renders the view @@ -199,8 +220,8 @@ void FGLRenderer::EndOffscreen() sector_t *FGLRenderer::RenderView(player_t* player) { - gl_RenderState.SetVertexBuffer(mVBO); - mVBO->Reset(); + gl_RenderState.SetVertexBuffer(screen->mVertexData); + screen->mVertexData->Reset(); sector_t *retsec; if (!V_IsHardwareRenderer()) @@ -210,6 +231,8 @@ sector_t *FGLRenderer::RenderView(player_t* player) } else { + iter_dlightf = iter_dlight = draw_dlight = draw_dlightf = 0; + checkBenchActive(); // reset statistics counters @@ -221,8 +244,8 @@ sector_t *FGLRenderer::RenderView(player_t* player) P_FindParticleSubsectors(); - mLights->Clear(); - mViewpoints->Clear(); + screen->mLights->Clear(); + screen->mViewpoints->Clear(); // NoInterpolateView should have no bearing on camera textures, but needs to be preserved for the main view below. bool saved_niv = NoInterpolateView; @@ -244,7 +267,7 @@ sector_t *FGLRenderer::RenderView(player_t* player) fovratio = ratio; } - mShadowMap.Update(); + UpdateShadowMap(); retsec = RenderViewpoint(r_viewpoint, player->camera, NULL, r_viewpoint.FieldOfView.Degrees, ratio, fovratio, true, true); } All.Unclock(); @@ -314,17 +337,17 @@ void FGLRenderer::WriteSavePic (player_t *player, FileWriter *file, int width, i bounds.width = width; bounds.height = height; - // if mVBO is persistently mapped we must be sure the GPU finished reading from it before we fill it with new data. + // we must be sure the GPU finished reading from the buffer before we fill it with new data. glFinish(); // Switch to render buffers dimensioned for the savepic mBuffers = mSaveBuffers; P_FindParticleSubsectors(); // make sure that all recently spawned particles have a valid subsector. - gl_RenderState.SetVertexBuffer(mVBO); - mVBO->Reset(); - mLights->Clear(); - mViewpoints->Clear(); + gl_RenderState.SetVertexBuffer(screen->mVertexData); + screen->mVertexData->Reset(); + screen->mLights->Clear(); + screen->mViewpoints->Clear(); // This shouldn't overwrite the global viewpoint even for a short time. FRenderViewpoint savevp; @@ -358,208 +381,4 @@ void FGLRenderer::BeginFrame() mSaveBuffers->Setup(SAVEPICWIDTH, SAVEPICHEIGHT, SAVEPICWIDTH, SAVEPICHEIGHT); } -//=========================================================================== -// -// Vertex buffer for 2D drawer -// -//=========================================================================== - -class F2DVertexBuffer : public FSimpleVertexBuffer -{ - uint32_t ibo_id; - - // Make sure we can build upon FSimpleVertexBuffer. - static_assert(offsetof(FSimpleVertex, x) == offsetof(F2DDrawer::TwoDVertex, x), "x not aligned"); - static_assert(offsetof(FSimpleVertex, u) == offsetof(F2DDrawer::TwoDVertex, u), "u not aligned"); - static_assert(offsetof(FSimpleVertex, color) == offsetof(F2DDrawer::TwoDVertex, color0), "color not aligned"); - -public: - - F2DVertexBuffer() - { - glGenBuffers(1, &ibo_id); - } - ~F2DVertexBuffer() - { - if (ibo_id != 0) - { - glDeleteBuffers(1, &ibo_id); - } - } - void UploadData(F2DDrawer::TwoDVertex *vertices, int vertcount, int *indices, int indexcount) - { - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glBufferData(GL_ARRAY_BUFFER, vertcount * sizeof(vertices[0]), vertices, GL_STREAM_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexcount * sizeof(indices[0]), indices, GL_STREAM_DRAW); - } - - void BindVBO() override - { - FSimpleVertexBuffer::BindVBO(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id); - } -}; - -//=========================================================================== -// -// Draws the 2D stuff. This is the version for OpenGL 3 and later. -// -//=========================================================================== - -CVAR(Bool, gl_aalines, false, CVAR_ARCHIVE) - -void FGLRenderer::Draw2D(F2DDrawer *drawer) -{ - twoD.Clock(); - FGLDebug::PushGroup("Draw2D"); - if (VRMode::GetVRMode(true)->mEyeCount == 1) - mBuffers->BindCurrentFB(); - const auto &mScreenViewport = screen->mScreenViewport; - glViewport(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height); - GLRenderer->mViewpoints->Set2D(screen->GetWidth(), screen->GetHeight()); - - glDisable(GL_DEPTH_TEST); - - // Korshun: ENABLE AUTOMAP ANTIALIASING!!! - if (gl_aalines) - glEnable(GL_LINE_SMOOTH); - else - { - glDisable(GL_MULTISAMPLE); - glDisable(GL_LINE_SMOOTH); - glLineWidth(1.0); - } - - - auto &vertices = drawer->mVertices; - auto &indices = drawer->mIndices; - auto &commands = drawer->mData; - - if (commands.Size() == 0) - { - twoD.Unclock(); - return; - } - - for (auto &v : vertices) - { - // Change from BGRA to RGBA - std::swap(v.color0.r, v.color0.b); - } - auto vb = new F2DVertexBuffer; - vb->UploadData(&vertices[0], vertices.Size(), &indices[0], indices.Size()); - gl_RenderState.SetVertexBuffer(vb); - gl_RenderState.EnableFog(false); - - for(auto &cmd : commands) - { - - int gltrans = -1; - int tm, sb, db, be; - // The texture mode being returned here cannot be used, because the higher level code - // already manipulated the data so that some cases will not be handled correctly. - // Since we already get a proper mode from the calling code this doesn't really matter. - gl_GetRenderStyle(cmd.mRenderStyle, false, false, &tm, &sb, &db, &be); - gl_RenderState.BlendEquation(be); - gl_RenderState.BlendFunc(sb, db); - gl_RenderState.EnableBrightmap(!(cmd.mRenderStyle.Flags & STYLEF_ColorIsFixed)); - gl_RenderState.EnableFog(2); // Special 2D mode 'fog'. - - // Rather than adding remapping code, let's enforce that the constants here are equal. - static_assert(int(F2DDrawer::DTM_Normal) == int(TM_MODULATE), "DTM_Normal != TM_MODULATE"); - static_assert(int(F2DDrawer::DTM_Opaque) == int(TM_OPAQUE), "DTM_Opaque != TM_OPAQUE"); - static_assert(int(F2DDrawer::DTM_Invert) == int(TM_INVERSE), "DTM_Invert != TM_INVERSE"); - static_assert(int(F2DDrawer::DTM_InvertOpaque) == int(TM_INVERTOPAQUE), "DTM_InvertOpaque != TM_INVERTOPAQUE"); - static_assert(int(F2DDrawer::DTM_Stencil) == int(TM_MASK), "DTM_Stencil != TM_MASK"); - static_assert(int(F2DDrawer::DTM_AlphaTexture) == int(TM_REDTOALPHA), "DTM_AlphaTexture != TM_REDTOALPHA"); - gl_RenderState.SetTextureMode(cmd.mDrawMode); - if (cmd.mFlags & F2DDrawer::DTF_Scissor) - { - glEnable(GL_SCISSOR_TEST); - // scissor test doesn't use the current viewport for the coordinates, so use real screen coordinates - // Note that the origin here is the lower left corner! - auto sciX = screen->ScreenToWindowX(cmd.mScissor[0]); - auto sciY = screen->ScreenToWindowY(cmd.mScissor[3]); - auto sciW = screen->ScreenToWindowX(cmd.mScissor[2]) - sciX; - auto sciH = screen->ScreenToWindowY(cmd.mScissor[1]) - sciY; - glScissor(sciX, sciY, sciW, sciH); - } - else glDisable(GL_SCISSOR_TEST); - - if (cmd.mSpecialColormap[0].a != 0) - { - gl_RenderState.SetTextureMode(TM_FIXEDCOLORMAP); - gl_RenderState.SetObjectColor(cmd.mSpecialColormap[0]); - gl_RenderState.SetObjectColor2(cmd.mSpecialColormap[1]); - } - gl_RenderState.SetFog(cmd.mColor1, 0); - gl_RenderState.SetColor(1, 1, 1, 1, cmd.mDesaturate); - - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - - if (cmd.mTexture != nullptr) - { - auto mat = FMaterial::ValidateTexture(cmd.mTexture, false); - if (mat == nullptr) continue; - - if (gltrans == -1 && cmd.mTranslation != nullptr) gltrans = cmd.mTranslation->GetUniqueIndex(); - gl_RenderState.SetMaterial(mat, cmd.mFlags & F2DDrawer::DTF_Wrap ? CLAMP_NONE : CLAMP_XY_NOMIP, -gltrans, -1, cmd.mDrawMode == F2DDrawer::DTM_AlphaTexture); - gl_RenderState.EnableTexture(true); - - // Canvas textures are stored upside down - if (cmd.mTexture->bHasCanvas) - { - gl_RenderState.mTextureMatrix.loadIdentity(); - gl_RenderState.mTextureMatrix.scale(1.f, -1.f, 1.f); - gl_RenderState.mTextureMatrix.translate(0.f, 1.f, 0.0f); - gl_RenderState.EnableTextureMatrix(true); - } - if (cmd.mFlags & F2DDrawer::DTF_Burn) - { - gl_RenderState.SetEffect(EFF_BURN); - } - } - else - { - gl_RenderState.EnableTexture(false); - } - gl_RenderState.Apply(); - - switch (cmd.mType) - { - case F2DDrawer::DrawTypeTriangles: - glDrawElements(GL_TRIANGLES, cmd.mIndexCount, GL_UNSIGNED_INT, (const void *)(cmd.mIndexIndex * sizeof(unsigned int))); - break; - - case F2DDrawer::DrawTypeLines: - glDrawArrays(GL_LINES, cmd.mVertIndex, cmd.mVertCount); - break; - - case F2DDrawer::DrawTypePoints: - glDrawArrays(GL_POINTS, cmd.mVertIndex, cmd.mVertCount); - break; - - } - gl_RenderState.SetObjectColor(0xffffffff); - gl_RenderState.SetObjectColor2(0); - gl_RenderState.EnableTextureMatrix(false); - gl_RenderState.SetEffect(EFF_NONE); - - } - glDisable(GL_SCISSOR_TEST); - - gl_RenderState.BlendEquation(GL_FUNC_ADD); - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.SetVertexBuffer(mVBO); - gl_RenderState.EnableTexture(true); - gl_RenderState.EnableBrightmap(true); - gl_RenderState.SetTextureMode(TM_MODULATE); - gl_RenderState.EnableFog(false); - gl_RenderState.ResetColor(); - gl_RenderState.Apply(); - delete vb; - FGLDebug::PopGroup(); - twoD.Unclock(); -} +} \ No newline at end of file diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index 3a48a9ccb..67d4fba28 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -6,8 +6,9 @@ #include "vectors.h" #include "r_renderer.h" #include "r_data/matrix.h" +#include "gl/renderer/gl_renderbuffers.h" #include "hwrenderer/scene/hw_portal.h" -#include "gl/dynlights/gl_shadowmap.h" +#include "hwrenderer/dynlights/hw_shadowmap.h" #include #ifdef _MSC_VER @@ -18,12 +19,9 @@ struct particle_t; class FCanvasTexture; class FFlatVertexBuffer; class FSkyVertexBuffer; -class OpenGLFrameBuffer; -struct FDrawInfo; class FShaderManager; -class GLPortal; +class HWPortal; class FLightBuffer; -class FSamplerManager; class DPSprite; class FGLRenderBuffers; class FPresentShader; @@ -33,19 +31,18 @@ class FPresent3DRowShader; class FGL2DDrawer; class FHardwareTexture; class FShadowMapShader; -class FCustomPostProcessShaders; class SWSceneDrawer; class GLViewpointBuffer; struct FRenderViewpoint; -#define NOQUEUE nullptr // just some token to be used as a placeholder +class FPresentShaderBase; -enum +namespace OpenGLRenderer { - DM_MAINVIEW, - DM_OFFSCREEN, - DM_PORTAL, - DM_SKYPORTAL -}; + class FSamplerManager; + class FCustomPostProcessShaders; + class OpenGLFrameBuffer; + +#define NOQUEUE nullptr // just some token to be used as a placeholder class FGLRenderer { @@ -59,6 +56,7 @@ public: unsigned int mFBID; unsigned int mVAOID; unsigned int PortalQueryObject; + unsigned int mStencilValue = 0; int mOldFBID; @@ -72,20 +70,10 @@ public: FShadowMapShader *mShadowMapShader = nullptr; FCustomPostProcessShaders *mCustomPostProcessShaders = nullptr; - FShadowMap mShadowMap; - //FRotator mAngles; - FFlatVertexBuffer *mVBO = nullptr; - FSkyVertexBuffer *mSkyVBO = nullptr; - FLightBuffer *mLights = nullptr; - GLViewpointBuffer *mViewpoints = nullptr; SWSceneDrawer *swdrawer = nullptr; - FPortalSceneState mPortalState; - - float mSceneClearColor[3]; - FGLRenderer(OpenGLFrameBuffer *fb); ~FGLRenderer() ; @@ -93,7 +81,6 @@ public: void ClearBorders(); - void SetupLevel(); void ResetSWScene(); void PresentStereo(); @@ -111,14 +98,28 @@ public: sector_t *RenderView(player_t *player); void BeginFrame(); - void Set3DViewport(); sector_t *RenderViewpoint (FRenderViewpoint &mainvp, AActor * camera, IntRect * bounds, float fov, float ratio, float fovratio, bool mainview, bool toscreen); bool StartOffscreen(); void EndOffscreen(); + void UpdateShadowMap(); void BindToFrameBuffer(FMaterial *mat); + +private: + + void DrawScene(HWDrawInfo *di, int drawmode); + bool QuadStereoCheckInitialRenderContextState(); + void PresentAnaglyph(bool r, bool g, bool b); + void PresentSideBySide(); + void PresentTopBottom(); + void prepareInterleavedPresent(FPresentShaderBase& shader); + void PresentColumnInterleaved(); + void PresentRowInterleaved(); + void PresentCheckerInterleaved(); + void PresentQuadStereo(); + }; #include "hwrenderer/scene/hw_fakeflat.h" @@ -133,4 +134,5 @@ struct TexFilter_s extern FGLRenderer *GLRenderer; +} #endif diff --git a/src/gl/renderer/gl_renderstate.cpp b/src/gl/renderer/gl_renderstate.cpp index e585296f6..1bfed5fc6 100644 --- a/src/gl/renderer/gl_renderstate.cpp +++ b/src/gl/renderer/gl_renderstate.cpp @@ -30,20 +30,22 @@ #include "r_data/colormaps.h" #include "gl_load/gl_system.h" #include "gl_load/gl_interface.h" -#include "gl/data/gl_vertexbuffer.h" #include "hwrenderer/utility/hw_cvars.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/scene/hw_skydome.h" #include "gl/shaders/gl_shader.h" #include "gl/renderer/gl_renderer.h" -#include "gl/dynlights//gl_lightbuffer.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" #include "gl/renderer/gl_renderbuffers.h" #include "gl/textures/gl_hwtexture.h" +#include "gl/system/gl_buffers.h" +#include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/data/hw_viewpointbuffer.h" -void gl_SetTextureMode(int type); - -FRenderState gl_RenderState; - -CVAR(Bool, gl_direct_state_change, true, 0) +namespace OpenGLRenderer +{ +FGLRenderState gl_RenderState; static VSMatrix identityMatrix(1); TArray gl_MatrixStack; @@ -55,57 +57,33 @@ static void matrixToGL(const VSMatrix &mat, int loc) //========================================================================== // -// +// This only gets called once upon setup. +// With OpenGL the state is persistent and cannot be cleared, once set up. // //========================================================================== -void FRenderState::Reset() +void FGLRenderState::Reset() { - mTextureEnabled = true; - mSplitEnabled = mBrightmapEnabled = mFogEnabled = mGlowEnabled = false; - mColorMask[0] = mColorMask[1] = mColorMask[2] = mColorMask[3] = true; - currentColorMask[0] = currentColorMask[1] = currentColorMask[2] = currentColorMask[3] = true; - mFogColor.d = -1; - mTextureMode = -1; - mDesaturation = 0; - mSrcBlend = GL_SRC_ALPHA; - mDstBlend = GL_ONE_MINUS_SRC_ALPHA; - mAlphaThreshold = 0.5f; - mBlendEquation = GL_FUNC_ADD; - mModelMatrixEnabled = false; - mTextureMatrixEnabled = false; - mObjectColor = 0xffffffff; - mObjectColor2 = 0; - mVertexBuffer = mCurrentVertexBuffer = NULL; - mSoftLight = 0; - mLightParms[0] = mLightParms[1] = mLightParms[2] = 0.0f; - mLightParms[3] = -1.f; - mSpecialEffect = EFF_NONE; + FRenderState::Reset(); + mVertexBuffer = mCurrentVertexBuffer = nullptr; mGlossiness = 0.0f; mSpecularLevel = 0.0f; mShaderTimer = 0.0f; - ClearClipSplit(); + stRenderStyle = DefaultRenderStyle(); stSrcBlend = stDstBlend = -1; stBlendEquation = -1; - stAlphaThreshold = -1.f; stAlphaTest = 0; mLastDepthClamp = true; - mInterpolationFactor = 0.0f; - mColor.Set(1.0f, 1.0f, 1.0f, 1.0f); - mGlowTop.Set(0.0f, 0.0f, 0.0f, 0.0f); - mGlowBottom.Set(0.0f, 0.0f, 0.0f, 0.0f); - mGlowTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); - mGlowBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); - mSplitTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); - mSplitBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); - mDynColor.Set(0.0f, 0.0f, 0.0f, 0.0f); mEffectState = 0; activeShader = nullptr; - mModelMatrix.loadIdentity(); - mTextureMatrix.loadIdentity(); mPassType = NORMAL_PASS; + + mCurrentVertexBuffer = nullptr; + mCurrentVertexOffsets[0] = mVertexOffsets[0] = 0; + mCurrentIndexBuffer = nullptr; + } //========================================================================== @@ -114,7 +92,7 @@ void FRenderState::Reset() // //========================================================================== -bool FRenderState::ApplyShader() +bool FGLRenderState::ApplyShader() { static uint64_t firstFrame = 0; // if firstFrame is not yet initialized, initialize it to current time @@ -156,7 +134,7 @@ bool FRenderState::ApplyShader() activeShader->muDesaturation.Set(mDesaturation / 255.f); activeShader->muFogEnabled.Set(fogset); - activeShader->muTextureMode.Set(mTextureMode == TM_MODULATE && mTempTM == TM_OPAQUE ? TM_OPAQUE : mTextureMode); + activeShader->muTextureMode.Set(mTextureMode == TM_NORMAL && mTempTM == TM_OPAQUE ? TM_OPAQUE : mTextureMode); activeShader->muLightParms.Set(mLightParms); activeShader->muFogColor.Set(mFogColor); activeShader->muObjectColor.Set(mObjectColor); @@ -226,6 +204,10 @@ bool FRenderState::ApplyShader() matrixToGL(identityMatrix, activeShader->modelmatrix_index); matrixToGL(identityMatrix, activeShader->normalmodelmatrix_index); } + + auto index = screen->mLights->BindUBO(mLightIndex); + activeShader->muLightIndex.Set(index); + return true; } @@ -236,67 +218,81 @@ bool FRenderState::ApplyShader() // //========================================================================== -void FRenderState::Apply() +void FGLRenderState::ApplyState() { - if (!gl_direct_state_change) + if (mRenderStyle != stRenderStyle) { - if (mSrcBlend != stSrcBlend || mDstBlend != stDstBlend) - { - stSrcBlend = mSrcBlend; - stDstBlend = mDstBlend; - glBlendFunc(mSrcBlend, mDstBlend); - } - if (mBlendEquation != stBlendEquation) - { - stBlendEquation = mBlendEquation; - glBlendEquation(mBlendEquation); - } + ApplyBlendMode(); + stRenderStyle = mRenderStyle; } - //ApplyColorMask(); I don't think this is needed. - - if (mVertexBuffer != mCurrentVertexBuffer) + if (mSplitEnabled != stSplitEnabled) { - if (mVertexBuffer == NULL) glBindBuffer(GL_ARRAY_BUFFER, 0); - else mVertexBuffer->BindVBO(); + if (mSplitEnabled) + { + glEnable(GL_CLIP_DISTANCE3); + glEnable(GL_CLIP_DISTANCE4); + } + else + { + glDisable(GL_CLIP_DISTANCE3); + glDisable(GL_CLIP_DISTANCE4); + } + stSplitEnabled = mSplitEnabled; + } + + if (mMaterial.mChanged) + { + ApplyMaterial(mMaterial.mMaterial, mMaterial.mClampMode, mMaterial.mTranslation, mMaterial.mOverrideShader); + mMaterial.mChanged = false; + } + + if (mBias.mChanged) + { + if (mBias.mFactor == 0 && mBias.mUnits == 0) + { + glDisable(GL_POLYGON_OFFSET_FILL); + } + else + { + glEnable(GL_POLYGON_OFFSET_FILL); + } + glPolygonOffset(mBias.mFactor, mBias.mUnits); + mBias.mChanged = false; + } +} + +void FGLRenderState::ApplyBuffers() +{ + if (mVertexBuffer != mCurrentVertexBuffer || mVertexOffsets[0] != mCurrentVertexOffsets[0] || mVertexOffsets[1] != mCurrentVertexOffsets[1]) + { + assert(mVertexBuffer != nullptr); + static_cast(mVertexBuffer)->Bind(mVertexOffsets); mCurrentVertexBuffer = mVertexBuffer; + mCurrentVertexOffsets[0] = mVertexOffsets[0]; + mCurrentVertexOffsets[1] = mVertexOffsets[1]; } + if (mIndexBuffer != mCurrentIndexBuffer) + { + if (mIndexBuffer) static_cast(mIndexBuffer)->Bind(); + mCurrentIndexBuffer = mIndexBuffer; + } +} + +void FGLRenderState::Apply() +{ + ApplyState(); + ApplyBuffers(); ApplyShader(); } - - -void FRenderState::ApplyColorMask() -{ - if ((mColorMask[0] != currentColorMask[0]) || - (mColorMask[1] != currentColorMask[1]) || - (mColorMask[2] != currentColorMask[2]) || - (mColorMask[3] != currentColorMask[3])) - { - glColorMask(mColorMask[0], mColorMask[1], mColorMask[2], mColorMask[3]); - currentColorMask[0] = mColorMask[0]; - currentColorMask[1] = mColorMask[1]; - currentColorMask[2] = mColorMask[2]; - currentColorMask[3] = mColorMask[3]; - } -} - -void FRenderState::ApplyLightIndex(int index) -{ - if (index > -1 && GLRenderer->mLights->GetBufferType() == GL_UNIFORM_BUFFER) - { - index = GLRenderer->mLights->BindUBO(index); - } - activeShader->muLightIndex.Set(index); -} - //=========================================================================== // // Binds a texture to the renderer // //=========================================================================== -void FRenderState::SetMaterial(FMaterial *mat, int clampmode, int translation, int overrideshader, bool alphatexture) +void FGLRenderState::ApplyMaterial(FMaterial *mat, int clampmode, int translation, int overrideshader) { if (mat->tex->bHasCanvas) { @@ -304,9 +300,9 @@ void FRenderState::SetMaterial(FMaterial *mat, int clampmode, int translation, i } else { - mTempTM = TM_MODULATE; + mTempTM = TM_NORMAL; } - mEffectState = overrideshader >= 0 ? overrideshader : mat->mShaderIndex; + mEffectState = overrideshader >= 0 ? overrideshader : mat->GetShaderIndex(); mShaderTimer = mat->tex->shaderspeed; SetSpecular(mat->tex->Glossiness, mat->tex->SpecularLevel); @@ -347,4 +343,240 @@ void FRenderState::SetMaterial(FMaterial *mat, int clampmode, int translation, i } } +//========================================================================== +// +// Apply blend mode from RenderStyle +// +//========================================================================== +void FGLRenderState::ApplyBlendMode() +{ + static int blendstyles[] = { GL_ZERO, GL_ONE, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, }; + static int renderops[] = { 0, GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; + + int srcblend = blendstyles[mRenderStyle.SrcAlpha%STYLEALPHA_MAX]; + int dstblend = blendstyles[mRenderStyle.DestAlpha%STYLEALPHA_MAX]; + int blendequation = renderops[mRenderStyle.BlendOp & 15]; + + if (blendequation == -1) // This was a fuzz style. + { + srcblend = GL_DST_COLOR; + dstblend = GL_ONE_MINUS_SRC_ALPHA; + blendequation = GL_FUNC_ADD; + } + + // Checks must be disabled until all draw code has been converted. + //if (srcblend != stSrcBlend || dstblend != stDstBlend) + { + stSrcBlend = srcblend; + stDstBlend = dstblend; + glBlendFunc(srcblend, dstblend); + } + //if (blendequation != stBlendEquation) + { + stBlendEquation = blendequation; + glBlendEquation(blendequation); + } + +} + +//========================================================================== +// +// API dependent draw calls +// +//========================================================================== + +static int dt2gl[] = { GL_POINTS, GL_LINES, GL_TRIANGLES, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP }; + +void FGLRenderState::Draw(int dt, int index, int count, bool apply) +{ + if (apply) + { + Apply(); + } + drawcalls.Clock(); + glDrawArrays(dt2gl[dt], index, count); + drawcalls.Unclock(); +} + +void FGLRenderState::DrawIndexed(int dt, int index, int count, bool apply) +{ + if (apply) + { + Apply(); + } + drawcalls.Clock(); + glDrawElements(dt2gl[dt], count, GL_UNSIGNED_INT, (void*)(intptr_t)(index * sizeof(uint32_t))); + drawcalls.Unclock(); +} + +void FGLRenderState::SetDepthMask(bool on) +{ + glDepthMask(on); +} + +void FGLRenderState::SetDepthFunc(int func) +{ + static int df2gl[] = { GL_LESS, GL_LEQUAL, GL_ALWAYS }; + glDepthFunc(df2gl[func]); +} + +void FGLRenderState::SetDepthRange(float min, float max) +{ + glDepthRange(min, max); +} + +void FGLRenderState::EnableDrawBufferAttachments(bool on) +{ + EnableDrawBuffers(on ? GetPassDrawBufferCount() : 1); +} + +void FGLRenderState::SetStencil(int offs, int op, int flags) +{ + static int op2gl[] = { GL_KEEP, GL_INCR, GL_DECR }; + + glStencilFunc(GL_EQUAL, screen->stencilValue + offs, ~0); // draw sky into stencil + glStencilOp(GL_KEEP, GL_KEEP, op2gl[op]); // this stage doesn't modify the stencil + + bool cmon = !(flags & SF_ColorMaskOff); + bool cmalpha = cmon || (flags & SF_ColorMaskAlpha); + glColorMask(cmon, cmon, cmon, cmalpha); // don't write to the graphics buffer + glDepthMask(!(flags & SF_DepthMaskOff)); +} + +void FGLRenderState::ToggleState(int state, bool on) +{ + if (on) + { + glEnable(state); + } + else + { + glDisable(state); + } +} + +void FGLRenderState::SetCulling(int mode) +{ + if (mode != Cull_None) + { + glEnable(GL_CULL_FACE); + glFrontFace(mode == Cull_CCW ? GL_CCW : GL_CW); + } + else + { + glDisable(GL_CULL_FACE); + } +} + +void FGLRenderState::EnableClipDistance(int num, bool state) +{ + // Update the viewpoint-related clip plane setting. + if (!(gl.flags & RFL_NO_CLIP_PLANES)) + { + ToggleState(GL_CLIP_DISTANCE0 + num, state); + } +} + +void FGLRenderState::Clear(int targets) +{ + // This always clears to default values. + int gltarget = 0; + if (targets & CT_Depth) + { + gltarget |= GL_DEPTH_BUFFER_BIT; + glClearDepth(1); + } + if (targets & CT_Stencil) + { + gltarget |= GL_STENCIL_BUFFER_BIT; + glClearStencil(0); + } + if (targets & CT_Color) + { + gltarget |= GL_COLOR_BUFFER_BIT; + glClearColor(screen->mSceneClearColor[0], screen->mSceneClearColor[1], screen->mSceneClearColor[2], screen->mSceneClearColor[3]); + } + glClear(gltarget); +} + +void FGLRenderState::EnableStencil(bool on) +{ + ToggleState(GL_STENCIL_TEST, on); +} + +void FGLRenderState::SetScissor(int x, int y, int w, int h) +{ + if (w > -1) + { + glEnable(GL_SCISSOR_TEST); + glScissor(x, y, w, h); + } + else + { + glDisable(GL_SCISSOR_TEST); + } +} + +void FGLRenderState::SetViewport(int x, int y, int w, int h) +{ + glViewport(x, y, w, h); +} + +void FGLRenderState::EnableDepthTest(bool on) +{ + ToggleState(GL_DEPTH_TEST, on); +} + +void FGLRenderState::EnableMultisampling(bool on) +{ + ToggleState(GL_MULTISAMPLE, on); +} + +void FGLRenderState::EnableLineSmooth(bool on) +{ + ToggleState(GL_LINE_SMOOTH, on); +} + +//========================================================================== +// +// +// +//========================================================================== +void FGLRenderState::ClearScreen() +{ + bool multi = !!glIsEnabled(GL_MULTISAMPLE); + + screen->mViewpoints->Set2D(*this, SCREENWIDTH, SCREENHEIGHT); + SetColor(0, 0, 0); + Apply(); + + glDisable(GL_MULTISAMPLE); + glDisable(GL_DEPTH_TEST); + + glDrawArrays(GL_TRIANGLE_STRIP, FFlatVertexBuffer::FULLSCREEN_INDEX, 4); + + glEnable(GL_DEPTH_TEST); + if (multi) glEnable(GL_MULTISAMPLE); +} + + + +//========================================================================== +// +// Below are less frequently altrered state settings which do not get +// buffered by the state object, but set directly instead. +// +//========================================================================== + +bool FGLRenderState::SetDepthClamp(bool on) +{ + bool res = mLastDepthClamp; + if (!on) glDisable(GL_DEPTH_CLAMP); + else glEnable(GL_DEPTH_CLAMP); + mLastDepthClamp = on; + return res; +} + +} \ No newline at end of file diff --git a/src/gl/renderer/gl_renderstate.h b/src/gl/renderer/gl_renderstate.h index c046d3f61..7f6ac8af1 100644 --- a/src/gl/renderer/gl_renderstate.h +++ b/src/gl/renderer/gl_renderstate.h @@ -27,44 +27,20 @@ #include "gl_load/gl_interface.h" #include "r_data/matrix.h" #include "hwrenderer/scene//hw_drawstructs.h" +#include "hwrenderer/scene//hw_renderstate.h" #include "hwrenderer/textures/hw_material.h" #include "c_cvars.h" #include "r_defs.h" #include "r_data/r_translate.h" #include "g_levellocals.h" -class FVertexBuffer; +namespace OpenGLRenderer +{ + class FShader; struct GLSectorPlane; extern TArray gl_MatrixStack; -EXTERN_CVAR(Bool, gl_direct_state_change) - -struct FStateVec4 -{ - float vec[4]; - - void Set(float r, float g, float b, float a) - { - vec[0] = r; - vec[1] = g; - vec[2] = b; - vec[3] = a; - } -}; - - -enum EEffect -{ - EFF_NONE=-1, - EFF_FOGBOUNDARY, - EFF_SPHEREMAP, - EFF_BURN, - EFF_STENCIL, - - MAX_EFFECTS -}; - enum EPassType { NORMAL_PASS, @@ -72,49 +48,21 @@ enum EPassType MAX_PASS_TYPES }; -class FRenderState + +class FGLRenderState : public FRenderState { - friend void gl_SetTextureMode(int type); - bool mTextureEnabled; - uint8_t mFogEnabled; - bool mGlowEnabled; - bool mSplitEnabled; - bool mBrightmapEnabled; - bool mColorMask[4]; - bool currentColorMask[4]; - int mSpecialEffect; - int mTextureMode; - int mDesaturation; - int mSoftLight; - float mLightParms[4]; - int mSrcBlend, mDstBlend; - float mAlphaThreshold; - int mBlendEquation; - bool mModelMatrixEnabled; - bool mTextureMatrixEnabled; - bool mLastDepthClamp; - float mInterpolationFactor; + uint8_t mLastDepthClamp : 1; + float mGlossiness, mSpecularLevel; float mShaderTimer; - FVertexBuffer *mVertexBuffer, *mCurrentVertexBuffer; - FStateVec4 mNormal; - FStateVec4 mColor; - FStateVec4 mGlowTop, mGlowBottom; - FStateVec4 mGlowTopPlane, mGlowBottomPlane; - FStateVec4 mSplitTopPlane, mSplitBottomPlane; - PalEntry mFogColor; - PalEntry mObjectColor; - PalEntry mObjectColor2; - FStateVec4 mDynColor; - float mClipSplit[2]; - int mEffectState; - int mTempTM = TM_MODULATE; + int mTempTM = TM_NORMAL; - float stAlphaThreshold; + FRenderStyle stRenderStyle; int stSrcBlend, stDstBlend; bool stAlphaTest; + bool stSplitEnabled; int stBlendEquation; FShader *activeShader; @@ -123,6 +71,7 @@ class FRenderState int mNumDrawBuffers = 1; bool ApplyShader(); + void ApplyState(); // Texture binding state FMaterial *lastMaterial = nullptr; @@ -130,13 +79,14 @@ class FRenderState int lastTranslation = 0; int maxBoundMaterial = -1; + IVertexBuffer *mCurrentVertexBuffer; + int mCurrentVertexOffsets[2]; // one per binding point + IIndexBuffer *mCurrentIndexBuffer; + public: - VSMatrix mModelMatrix; - VSMatrix mTextureMatrix; - - FRenderState() + FGLRenderState() { Reset(); } @@ -148,183 +98,17 @@ public: lastMaterial = nullptr; } - void SetMaterial(FMaterial *mat, int clampmode, int translation, int overrideshader, bool alphatexture); + void ApplyMaterial(FMaterial *mat, int clampmode, int translation, int overrideshader); void Apply(); - void ApplyColorMask(); - void ApplyLightIndex(int index); - - void SetVertexBuffer(FVertexBuffer *vb) - { - mVertexBuffer = vb; - } + void ApplyBuffers(); + void ApplyBlendMode(); void ResetVertexBuffer() { // forces rebinding with the next 'apply' call. - mCurrentVertexBuffer = NULL; - } - - void SetNormal(FVector3 norm) - { - mNormal.Set(norm.X, norm.Y, norm.Z, 0.f); - } - - void SetNormal(float x, float y, float z) - { - mNormal.Set(x, y, z, 0.f); - } - - void SetColor(float r, float g, float b, float a = 1.f, int desat = 0) - { - mColor.Set(r, g, b, a); - mDesaturation = desat; - } - - void SetColor(PalEntry pe, int desat = 0) - { - mColor.Set(pe.r/255.f, pe.g/255.f, pe.b/255.f, pe.a/255.f); - mDesaturation = desat; - } - - void SetColorAlpha(PalEntry pe, float alpha = 1.f, int desat = 0) - { - mColor.Set(pe.r/255.f, pe.g/255.f, pe.b/255.f, alpha); - mDesaturation = desat; - } - - void ResetColor() - { - mColor.Set(1,1,1,1); - mDesaturation = 0; - } - - void GetColorMask(bool& r, bool &g, bool& b, bool& a) const - { - r = mColorMask[0]; - g = mColorMask[1]; - b = mColorMask[2]; - a = mColorMask[3]; - } - - void SetColorMask(bool r, bool g, bool b, bool a) - { - mColorMask[0] = r; - mColorMask[1] = g; - mColorMask[2] = b; - mColorMask[3] = a; - } - - void ResetColorMask() - { - for (int i = 0; i < 4; ++i) - mColorMask[i] = true; - } - - void SetTextureMode(int mode) - { - mTextureMode = mode; - } - - int GetTextureMode() - { - return mTextureMode; - } - - void EnableTexture(bool on) - { - mTextureEnabled = on; - } - - void EnableFog(uint8_t on) - { - mFogEnabled = on; - } - - void SetEffect(int eff) - { - mSpecialEffect = eff; - } - - void EnableGlow(bool on) - { - mGlowEnabled = on; - } - - void EnableSplit(bool on) - { - if (!(gl.flags & RFL_NO_CLIP_PLANES)) - { - mSplitEnabled = on; - if (on) - { - glEnable(GL_CLIP_DISTANCE3); - glEnable(GL_CLIP_DISTANCE4); - } - else - { - glDisable(GL_CLIP_DISTANCE3); - glDisable(GL_CLIP_DISTANCE4); - } - } - } - - void EnableBrightmap(bool on) - { - mBrightmapEnabled = on; - } - - void EnableModelMatrix(bool on) - { - mModelMatrixEnabled = on; - } - - void EnableTextureMatrix(bool on) - { - mTextureMatrixEnabled = on; - } - - void SetGlowParams(float *t, float *b) - { - mGlowTop.Set(t[0], t[1], t[2], t[3]); - mGlowBottom.Set(b[0], b[1], b[2], b[3]); - } - - void SetSoftLightLevel(int llevel, int blendfactor = 0) - { - if (level.lightmode == 8 && blendfactor == 0) mLightParms[3] = llevel / 255.f; - else mLightParms[3] = -1.f; - } - - void SetGlowPlanes(const secplane_t &top, const secplane_t &bottom) - { - DVector3 tn = top.Normal(); - DVector3 bn = bottom.Normal(); - mGlowTopPlane.Set(tn.X, tn.Y, 1. / tn.Z, top.fD()); - mGlowBottomPlane.Set(bn.X, bn.Y, 1. / bn.Z, bottom.fD()); - } - - void SetSplitPlanes(const secplane_t &top, const secplane_t &bottom) - { - DVector3 tn = top.Normal(); - DVector3 bn = bottom.Normal(); - mSplitTopPlane.Set(tn.X, tn.Y, 1. / tn.Z, top.fD()); - mSplitBottomPlane.Set(bn.X, bn.Y, 1. / bn.Z, bottom.fD()); - } - - void SetDynLight(float r, float g, float b) - { - mDynColor.Set(r, g, b, 0); - } - - void SetObjectColor(PalEntry pe) - { - mObjectColor = pe; - } - - void SetObjectColor2(PalEntry pe) - { - mObjectColor2 = pe; + mCurrentVertexBuffer = nullptr; + mCurrentIndexBuffer = nullptr; } void SetSpecular(float glossiness, float specularLevel) @@ -333,97 +117,6 @@ public: mSpecularLevel = specularLevel; } - void SetFog(PalEntry c, float d) - { - const float LOG2E = 1.442692f; // = 1/log(2) - mFogColor = c; - if (d >= 0.0f) mLightParms[2] = d * (-LOG2E / 64000.f); - } - - void SetLightParms(float f, float d) - { - mLightParms[1] = f; - mLightParms[0] = d; - } - - PalEntry GetFogColor() const - { - return mFogColor; - } - - void SetClipSplit(float bottom, float top) - { - mClipSplit[0] = bottom; - mClipSplit[1] = top; - } - - void SetClipSplit(float *vals) - { - memcpy(mClipSplit, vals, 2 * sizeof(float)); - } - - void GetClipSplit(float *out) - { - memcpy(out, mClipSplit, 2 * sizeof(float)); - } - - void ClearClipSplit() - { - mClipSplit[0] = -1000000.f; - mClipSplit[1] = 1000000.f; - } - - void BlendFunc(int src, int dst) - { - if (!gl_direct_state_change) - { - mSrcBlend = src; - mDstBlend = dst; - } - else - { - glBlendFunc(src, dst); - } - } - - void AlphaFunc(int func, float thresh) - { - if (func == GL_GREATER) mAlphaThreshold = thresh; - else mAlphaThreshold = thresh - 0.001f; - } - - void BlendEquation(int eq) - { - if (!gl_direct_state_change) - { - mBlendEquation = eq; - } - else - { - glBlendEquation(eq); - } - } - - // This wraps the depth clamp setting because we frequently need to read it which OpenGL is not particularly performant at... - bool SetDepthClamp(bool on) - { - bool res = mLastDepthClamp; - if (!on) glDisable(GL_DEPTH_CLAMP); - else glEnable(GL_DEPTH_CLAMP); - mLastDepthClamp = on; - return res; - } - - void SetInterpolationFactor(float fac) - { - mInterpolationFactor = fac; - } - - float GetInterpolationFactor() - { - return mInterpolationFactor; - } - void SetPassType(EPassType passType) { mPassType = passType; @@ -450,20 +143,33 @@ public: return mPassType == GBUFFER_PASS ? 3 : 1; } - // Backwards compatibility crap follows - void ApplyFixedFunction(); - void DrawColormapOverlay(); + void ToggleState(int state, bool on); + + void ClearScreen() override; + void Draw(int dt, int index, int count, bool apply = true) override; + void DrawIndexed(int dt, int index, int count, bool apply = true) override; + + bool SetDepthClamp(bool on) override; + void SetDepthMask(bool on) override; + void SetDepthFunc(int func) override; + void SetDepthRange(float min, float max) override; + void EnableDrawBufferAttachments(bool on) override; + void SetStencil(int offs, int op, int flags) override; + void SetCulling(int mode) override; + void EnableClipDistance(int num, bool state) override; + void Clear(int targets) override; + void EnableStencil(bool on) override; + void SetScissor(int x, int y, int w, int h) override; + void SetViewport(int x, int y, int w, int h) override; + void EnableDepthTest(bool on) override; + void EnableMultisampling(bool on) override; + void EnableLineSmooth(bool on) override; - void SetPlaneTextureRotation(GLSectorPlane *plane, FMaterial *texture) - { - if (hw_SetPlaneTextureRotation(plane, texture, mTextureMatrix)) - { - EnableTextureMatrix(true); - } - } }; -extern FRenderState gl_RenderState; +extern FGLRenderState gl_RenderState; + +} #endif diff --git a/src/gl/renderer/gl_scene.cpp b/src/gl/renderer/gl_scene.cpp new file mode 100644 index 000000000..5d54b57c9 --- /dev/null +++ b/src/gl/renderer/gl_scene.cpp @@ -0,0 +1,221 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2004-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_scene.cpp +** manages the rendering of the player's view +** +*/ + +#include "gl_load/gl_system.h" +#include "gi.h" +#include "m_png.h" +#include "doomstat.h" +#include "g_level.h" +#include "r_data/r_interpolate.h" +#include "r_utility.h" +#include "d_player.h" +#include "p_effect.h" +#include "sbar.h" +#include "po_man.h" +#include "p_local.h" +#include "serializer.h" +#include "g_levellocals.h" +#include "r_data/models/models.h" +#include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/data/flatvertices.h" + +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "gl_load/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_debug.h" +#include "hwrenderer/utility/hw_cvars.h" +#include "gl/renderer/gl_renderstate.h" +#include "gl/renderer/gl_renderbuffers.h" +#include "hwrenderer/data/hw_viewpointbuffer.h" +#include "hwrenderer/scene/hw_clipper.h" +#include "hwrenderer/scene/hw_portal.h" +#include "hwrenderer/utility/hw_vrmodes.h" +#include "gl/renderer/gl_renderer.h" + +//========================================================================== +// +// CVARs +// +//========================================================================== +CVAR(Bool, gl_texture, true, 0) +CVAR(Bool, gl_no_skyclear, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Float, gl_mask_threshold, 0.5f,CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Float, gl_mask_sprite_threshold, 0.5f,CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +EXTERN_CVAR (Bool, cl_capfps) +EXTERN_CVAR (Bool, r_deathcamera) +EXTERN_CVAR (Float, r_visibility) +EXTERN_CVAR (Bool, r_drawvoxels) + + +namespace OpenGLRenderer +{ + +//----------------------------------------------------------------------------- +// +// gl_drawscene - this function renders the scene from the current +// viewpoint, including mirrors and skyboxes and other portals +// It is assumed that the HWPortal::EndFrame returns with the +// stencil, z-buffer and the projection matrix intact! +// +//----------------------------------------------------------------------------- + +void FGLRenderer::DrawScene(HWDrawInfo *di, int drawmode) +{ + static int recursion=0; + static int ssao_portals_available = 0; + const auto &vp = di->Viewpoint; + + bool applySSAO = false; + if (drawmode == DM_MAINVIEW) + { + ssao_portals_available = gl_ssao_portals; + applySSAO = true; + } + else if (drawmode == DM_OFFSCREEN) + { + ssao_portals_available = 0; + } + else if (drawmode == DM_PORTAL && ssao_portals_available > 0) + { + applySSAO = true; + ssao_portals_available--; + } + + if (vp.camera != nullptr) + { + ActorRenderFlags savedflags = vp.camera->renderflags; + di->CreateScene(); + vp.camera->renderflags = savedflags; + } + else + { + di->CreateScene(); + } + + glDepthMask(true); + if (!gl_no_skyclear) screen->mPortalState->RenderFirstSkyPortal(recursion, di, gl_RenderState); + + di->RenderScene(gl_RenderState); + + if (applySSAO && gl_RenderState.GetPassType() == GBUFFER_PASS) + { + gl_RenderState.EnableDrawBuffers(1); + GLRenderer->AmbientOccludeScene(di->VPUniforms.mProjectionMatrix.get()[5]); + glViewport(screen->mSceneViewport.left, screen->mSceneViewport.top, screen->mSceneViewport.width, screen->mSceneViewport.height); + GLRenderer->mBuffers->BindSceneFB(true); + gl_RenderState.EnableDrawBuffers(gl_RenderState.GetPassDrawBufferCount()); + gl_RenderState.Apply(); + screen->mViewpoints->Bind(gl_RenderState, di->vpIndex); + } + + // Handle all portals after rendering the opaque objects but before + // doing all translucent stuff + recursion++; + screen->mPortalState->EndFrame(di, gl_RenderState); + recursion--; + di->RenderTranslucent(gl_RenderState); +} + +//----------------------------------------------------------------------------- +// +// Renders one viewpoint in a scene +// +//----------------------------------------------------------------------------- + +sector_t * FGLRenderer::RenderViewpoint (FRenderViewpoint &mainvp, AActor * camera, IntRect * bounds, float fov, float ratio, float fovratio, bool mainview, bool toscreen) +{ + R_SetupFrame (mainvp, r_viewwindow, camera); + + // Render (potentially) multiple views for stereo 3d + // Fixme. The view offsetting should be done with a static table and not require setup of the entire render state for the mode. + auto vrmode = VRMode::GetVRMode(mainview && toscreen); + for (int eye_ix = 0; eye_ix < vrmode->mEyeCount; ++eye_ix) + { + const auto &eye = vrmode->mEyes[eye_ix]; + screen->SetViewportRects(bounds); + + if (mainview) // Bind the scene frame buffer and turn on draw buffers used by ssao + { + FGLDebug::PushGroup("MainView"); + + bool useSSAO = (gl_ssao != 0); + mBuffers->BindSceneFB(useSSAO); + gl_RenderState.SetPassType(useSSAO ? GBUFFER_PASS : NORMAL_PASS); + gl_RenderState.EnableDrawBuffers(gl_RenderState.GetPassDrawBufferCount()); + gl_RenderState.Apply(); + } + + + auto di = HWDrawInfo::StartDrawInfo(nullptr, mainvp, nullptr); + auto &vp = di->Viewpoint; + + di->Set3DViewport(gl_RenderState); + di->SetViewArea(); + auto cm = di->SetFullbrightFlags(mainview ? vp.camera->player : nullptr); + di->Viewpoint.FieldOfView = fov; // Set the real FOV for the current scene (it's not necessarily the same as the global setting in r_viewpoint) + + // Stereo mode specific perspective projection + di->VPUniforms.mProjectionMatrix = eye.GetProjection(fov, ratio, fovratio); + // Stereo mode specific viewpoint adjustment + vp.Pos += eye.GetViewShift(vp.HWAngles.Yaw.Degrees); + di->SetupView(gl_RenderState, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, false, false); + + // std::function until this can be done better in a cross-API fashion. + di->ProcessScene(toscreen, [&](HWDrawInfo *di, int mode) { + DrawScene(di, mode); + }); + + if (mainview) + { + PostProcess.Clock(); + if (toscreen) di->EndDrawScene(mainvp.sector, gl_RenderState); // do not call this for camera textures. + + if (gl_RenderState.GetPassType() == GBUFFER_PASS) // Turn off ssao draw buffers + { + gl_RenderState.SetPassType(NORMAL_PASS); + gl_RenderState.EnableDrawBuffers(1); + } + + mBuffers->BlitSceneToTexture(); // Copy the resulting scene to the current post process texture + + FGLDebug::PopGroup(); // MainView + + PostProcessScene(cm, [&]() { di->DrawEndScene2D(mainvp.sector, gl_RenderState); }); + PostProcess.Unclock(); + } + di->EndDrawInfo(); + if (vrmode->mEyeCount > 1) + mBuffers->BlitToEyeTexture(eye_ix); + } + + interpolator.RestoreInterpolations (); + return mainvp.sector; +} + +} \ No newline at end of file diff --git a/src/gl/renderer/gl_stereo3d.cpp b/src/gl/renderer/gl_stereo3d.cpp index 2684622d8..6baaa801f 100644 --- a/src/gl/renderer/gl_stereo3d.cpp +++ b/src/gl/renderer/gl_stereo3d.cpp @@ -31,6 +31,7 @@ #include "hwrenderer/utility/hw_vrmodes.h" #include "gl/system/gl_framebuffer.h" #include "gl/renderer/gl_postprocessstate.h" +#include "gl/system/gl_framebuffer.h" #include "hwrenderer/postprocessing/hw_presentshader.h" #include "hwrenderer/postprocessing/hw_present3dRowshader.h" @@ -41,24 +42,27 @@ EXTERN_CVAR(Float, vid_contrast) EXTERN_CVAR(Int, gl_satformula) EXTERN_CVAR(Int, gl_dither_bpc) +namespace OpenGLRenderer +{ + //========================================================================== // // // //========================================================================== -static void PresentAnaglyph(bool r, bool g, bool b) +void FGLRenderer::PresentAnaglyph(bool r, bool g, bool b) { - GLRenderer->mBuffers->BindOutputFB(); - GLRenderer->ClearBorders(); + mBuffers->BindOutputFB(); + ClearBorders(); glColorMask(r, g, b, 1); - GLRenderer->mBuffers->BindEyeTexture(0, 0); - GLRenderer->DrawPresentTexture(screen->mOutputLetterbox, true); + mBuffers->BindEyeTexture(0, 0); + DrawPresentTexture(screen->mOutputLetterbox, true); glColorMask(!r, !g, !b, 1); - GLRenderer->mBuffers->BindEyeTexture(1, 0); - GLRenderer->DrawPresentTexture(screen->mOutputLetterbox, true); + mBuffers->BindEyeTexture(1, 0); + DrawPresentTexture(screen->mOutputLetterbox, true); glColorMask(1, 1, 1, 1); } @@ -69,10 +73,10 @@ static void PresentAnaglyph(bool r, bool g, bool b) // //========================================================================== -static void PresentSideBySide() +void FGLRenderer::PresentSideBySide() { - GLRenderer->mBuffers->BindOutputFB(); - GLRenderer->ClearBorders(); + mBuffers->BindOutputFB(); + ClearBorders(); // Compute screen regions to use for left and right eye views int leftWidth = screen->mOutputLetterbox.width / 2; @@ -83,11 +87,11 @@ static void PresentSideBySide() rightHalfScreen.width = rightWidth; rightHalfScreen.left += leftWidth; - GLRenderer->mBuffers->BindEyeTexture(0, 0); - GLRenderer->DrawPresentTexture(leftHalfScreen, true); + mBuffers->BindEyeTexture(0, 0); + DrawPresentTexture(leftHalfScreen, true); - GLRenderer->mBuffers->BindEyeTexture(1, 0); - GLRenderer->DrawPresentTexture(rightHalfScreen, true); + mBuffers->BindEyeTexture(1, 0); + DrawPresentTexture(rightHalfScreen, true); } @@ -97,10 +101,10 @@ static void PresentSideBySide() // //========================================================================== -static void PresentTopBottom() +void FGLRenderer::PresentTopBottom() { - GLRenderer->mBuffers->BindOutputFB(); - GLRenderer->ClearBorders(); + mBuffers->BindOutputFB(); + ClearBorders(); // Compute screen regions to use for left and right eye views int topHeight = screen->mOutputLetterbox.height / 2; @@ -112,11 +116,11 @@ static void PresentTopBottom() bottomHalfScreen.height = bottomHeight; bottomHalfScreen.top = 0; - GLRenderer->mBuffers->BindEyeTexture(0, 0); - GLRenderer->DrawPresentTexture(topHalfScreen, true); + mBuffers->BindEyeTexture(0, 0); + DrawPresentTexture(topHalfScreen, true); - GLRenderer->mBuffers->BindEyeTexture(1, 0); - GLRenderer->DrawPresentTexture(bottomHalfScreen, true); + mBuffers->BindEyeTexture(1, 0); + DrawPresentTexture(bottomHalfScreen, true); } //========================================================================== @@ -125,15 +129,15 @@ static void PresentTopBottom() // //========================================================================== -static void prepareInterleavedPresent(FPresentShaderBase& shader) +void FGLRenderer::prepareInterleavedPresent(FPresentShaderBase& shader) { - GLRenderer->mBuffers->BindOutputFB(); - GLRenderer->ClearBorders(); + mBuffers->BindOutputFB(); + ClearBorders(); // Bind each eye texture, for composition in the shader - GLRenderer->mBuffers->BindEyeTexture(0, 0); - GLRenderer->mBuffers->BindEyeTexture(1, 1); + mBuffers->BindEyeTexture(0, 0); + mBuffers->BindEyeTexture(1, 1); glActiveTexture(GL_TEXTURE0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -148,7 +152,7 @@ static void prepareInterleavedPresent(FPresentShaderBase& shader) shader.Bind(NOQUEUE); - if (GLRenderer->framebuffer->IsHWGammaActive()) + if (framebuffer->IsHWGammaActive()) { shader.Uniforms->InvGamma = 1.0f; shader.Uniforms->Contrast = 1.0f; @@ -165,8 +169,8 @@ static void prepareInterleavedPresent(FPresentShaderBase& shader) } shader.Uniforms->ColorScale = (gl_dither_bpc == -1) ? 255.0f : (float)((1 << gl_dither_bpc) - 1); shader.Uniforms->Scale = { - screen->mScreenViewport.width / (float)GLRenderer->mBuffers->GetWidth(), - screen->mScreenViewport.height / (float)GLRenderer->mBuffers->GetHeight() + screen->mScreenViewport.width / (float)mBuffers->GetWidth(), + screen->mScreenViewport.height / (float)mBuffers->GetHeight() }; shader.Uniforms.Set(); } @@ -177,11 +181,11 @@ static void prepareInterleavedPresent(FPresentShaderBase& shader) // //========================================================================== -static void PresentColumnInterleaved() +void FGLRenderer::PresentColumnInterleaved() { FGLPostProcessState savedState; savedState.SaveTextureBindings(2); - prepareInterleavedPresent(*GLRenderer->mPresent3dColumnShader); + prepareInterleavedPresent(*mPresent3dColumnShader); // Compute absolute offset from top of screen to top of current display window // because we need screen-relative, not window-relative, scan line parity @@ -191,10 +195,10 @@ static void PresentColumnInterleaved() //auto windowHOffset = clientoffset.X % 2; int windowHOffset = 0; - GLRenderer->mPresent3dColumnShader->Uniforms->WindowPositionParity = windowHOffset; - GLRenderer->mPresent3dColumnShader->Uniforms.Set(); + mPresent3dColumnShader->Uniforms->WindowPositionParity = windowHOffset; + mPresent3dColumnShader->Uniforms.Set(); - GLRenderer->RenderScreenQuad(); + RenderScreenQuad(); } //========================================================================== @@ -203,24 +207,24 @@ static void PresentColumnInterleaved() // //========================================================================== -static void PresentRowInterleaved() +void FGLRenderer::PresentRowInterleaved() { FGLPostProcessState savedState; savedState.SaveTextureBindings(2); - prepareInterleavedPresent(*GLRenderer->mPresent3dRowShader); + prepareInterleavedPresent(*mPresent3dRowShader); // Todo: //auto clientoffset = screen->GetClientOffset(); //auto windowVOffset = clientoffset.Y % 2; int windowVOffset = 0; - GLRenderer->mPresent3dRowShader->Uniforms->WindowPositionParity = + mPresent3dRowShader->Uniforms->WindowPositionParity = (windowVOffset + screen->mOutputLetterbox.height + 1 // +1 because of origin at bottom ) % 2; - GLRenderer->mPresent3dRowShader->Uniforms.Set(); - GLRenderer->RenderScreenQuad(); + mPresent3dRowShader->Uniforms.Set(); + RenderScreenQuad(); } //========================================================================== @@ -229,11 +233,11 @@ static void PresentRowInterleaved() // //========================================================================== -static void PresentCheckerInterleaved() +void FGLRenderer::PresentCheckerInterleaved() { FGLPostProcessState savedState; savedState.SaveTextureBindings(2); - prepareInterleavedPresent(*GLRenderer->mPresent3dCheckerShader); + prepareInterleavedPresent(*mPresent3dCheckerShader); // Compute absolute offset from top of screen to top of current display window // because we need screen-relative, not window-relative, scan line parity @@ -244,14 +248,14 @@ static void PresentCheckerInterleaved() int windowHOffset = 0; int windowVOffset = 0; - GLRenderer->mPresent3dCheckerShader->Uniforms->WindowPositionParity = + mPresent3dCheckerShader->Uniforms->WindowPositionParity = (windowVOffset + windowHOffset + screen->mOutputLetterbox.height + 1 // +1 because of origin at bottom ) % 2; // because we want the top pixel offset, but gl_FragCoord.y is the bottom pixel offset - GLRenderer->mPresent3dCheckerShader->Uniforms.Set(); - GLRenderer->RenderScreenQuad(); + mPresent3dCheckerShader->Uniforms.Set(); + RenderScreenQuad(); } //========================================================================== @@ -260,12 +264,12 @@ static void PresentCheckerInterleaved() // //========================================================================== -bool QuadStereoCheckInitialRenderContextState() +bool FGLRenderer::QuadStereoCheckInitialRenderContextState() { // Keep trying until we see at least one good OpenGL context to render to - static bool bQuadStereoSupported = false; - static bool bDecentContextWasFound = false; - static int contextCheckCount = 0; + bool bQuadStereoSupported = false; + bool bDecentContextWasFound = false; + int contextCheckCount = 0; if ((!bDecentContextWasFound) && (contextCheckCount < 200)) { contextCheckCount += 1; @@ -290,30 +294,30 @@ bool QuadStereoCheckInitialRenderContextState() // //========================================================================== -static void PresentQuadStereo() +void FGLRenderer::PresentQuadStereo() { if (QuadStereoCheckInitialRenderContextState()) { - GLRenderer->mBuffers->BindOutputFB(); + mBuffers->BindOutputFB(); glDrawBuffer(GL_BACK_LEFT); - GLRenderer->ClearBorders(); - GLRenderer->mBuffers->BindEyeTexture(0, 0); - GLRenderer->DrawPresentTexture(screen->mOutputLetterbox, true); + ClearBorders(); + mBuffers->BindEyeTexture(0, 0); + DrawPresentTexture(screen->mOutputLetterbox, true); glDrawBuffer(GL_BACK_RIGHT); - GLRenderer->ClearBorders(); - GLRenderer->mBuffers->BindEyeTexture(1, 0); - GLRenderer->DrawPresentTexture(screen->mOutputLetterbox, true); + ClearBorders(); + mBuffers->BindEyeTexture(1, 0); + DrawPresentTexture(screen->mOutputLetterbox, true); glDrawBuffer(GL_BACK); } else { - GLRenderer->mBuffers->BindOutputFB(); - GLRenderer->ClearBorders(); - GLRenderer->mBuffers->BindEyeTexture(0, 0); - GLRenderer->DrawPresentTexture(screen->mOutputLetterbox, true); + mBuffers->BindOutputFB(); + ClearBorders(); + mBuffers->BindEyeTexture(0, 0); + DrawPresentTexture(screen->mOutputLetterbox, true); } } @@ -364,3 +368,4 @@ void FGLRenderer::PresentStereo() } } +} \ No newline at end of file diff --git a/src/gl/scene/gl_drawinfo.cpp b/src/gl/scene/gl_drawinfo.cpp deleted file mode 100644 index 7cddfdec4..000000000 --- a/src/gl/scene/gl_drawinfo.cpp +++ /dev/null @@ -1,520 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2002-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_drawinfo.cpp -** Implements the draw info structure which contains most of the -** data in a scene and the draw lists - including a very thorough BSP -** style sorting algorithm for translucent objects. -** -*/ - -#include "gl_load/gl_system.h" -#include "r_sky.h" -#include "r_utility.h" -#include "doomstat.h" -#include "g_levellocals.h" -#include "tarray.h" -#include "hwrenderer/scene/hw_drawstructs.h" - -#include "gl/data/gl_vertexbuffer.h" -#include "gl/scene/gl_drawinfo.h" -#include "hwrenderer/scene/hw_clipper.h" -#include "gl/scene/gl_portal.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/renderer/gl_quaddrawer.h" -#include "gl/dynlights/gl_lightbuffer.h" - -class FDrawInfoList -{ -public: - TDeletingArray mList; - - - FDrawInfo * GetNew(); - void Release(FDrawInfo *); -}; - - -static FDrawInfo * gl_drawinfo; -FDrawInfoList di_list; - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::DoDrawSorted(HWDrawList *dl, SortNode * head) -{ - float clipsplit[2]; - int relation = 0; - float z = 0.f; - - gl_RenderState.GetClipSplit(clipsplit); - - if (dl->drawitems[head->itemindex].rendertype == GLDIT_FLAT) - { - z = dl->flats[dl->drawitems[head->itemindex].index]->z; - relation = z > Viewpoint.Pos.Z ? 1 : -1; - } - - - // left is further away, i.e. for stuff above viewz its z coordinate higher, for stuff below viewz its z coordinate is lower - if (head->left) - { - if (relation == -1) - { - gl_RenderState.SetClipSplit(clipsplit[0], z); // render below: set flat as top clip plane - } - else if (relation == 1) - { - gl_RenderState.SetClipSplit(z, clipsplit[1]); // render above: set flat as bottom clip plane - } - DoDrawSorted(dl, head->left); - gl_RenderState.SetClipSplit(clipsplit); - } - dl->DoDraw(this, GLPASS_TRANSLUCENT, head->itemindex, true); - if (head->equal) - { - SortNode * ehead=head->equal; - while (ehead) - { - dl->DoDraw(this, GLPASS_TRANSLUCENT, ehead->itemindex, true); - ehead=ehead->equal; - } - } - // right is closer, i.e. for stuff above viewz its z coordinate is lower, for stuff below viewz its z coordinate is higher - if (head->right) - { - if (relation == 1) - { - gl_RenderState.SetClipSplit(clipsplit[0], z); // render below: set flat as top clip plane - } - else if (relation == -1) - { - gl_RenderState.SetClipSplit(z, clipsplit[1]); // render above: set flat as bottom clip plane - } - DoDrawSorted(dl, head->right); - gl_RenderState.SetClipSplit(clipsplit); - } -} - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::DrawSorted(int listindex) -{ - HWDrawList *dl = &drawlists[listindex]; - if (dl->drawitems.Size()==0) return; - - if (!dl->sorted) - { - GLRenderer->mVBO->Map(); - dl->Sort(this); - GLRenderer->mVBO->Unmap(); - } - gl_RenderState.ClearClipSplit(); - if (!(gl.flags & RFL_NO_CLIP_PLANES)) - { - glEnable(GL_CLIP_DISTANCE1); - glEnable(GL_CLIP_DISTANCE2); - } - DoDrawSorted(dl, dl->sorted); - if (!(gl.flags & RFL_NO_CLIP_PLANES)) - { - glDisable(GL_CLIP_DISTANCE1); - glDisable(GL_CLIP_DISTANCE2); - } - gl_RenderState.ClearClipSplit(); -} - -//========================================================================== -// -// Try to reuse the lists as often as possible as they contain resources that -// are expensive to create and delete. -// -// Note: If multithreading gets used, this class needs synchronization. -// -//========================================================================== - -FDrawInfo *FDrawInfoList::GetNew() -{ - if (mList.Size() > 0) - { - FDrawInfo *di; - mList.Pop(di); - return di; - } - return new FDrawInfo; -} - -void FDrawInfoList::Release(FDrawInfo * di) -{ - di->ClearBuffers(); - mList.Push(di); -} - -//========================================================================== -// -// Sets up a new drawinfo struct -// -//========================================================================== - -// OpenGL has no use for multiple clippers so use the same one for all DrawInfos. -static Clipper staticClipper; - -FDrawInfo *FDrawInfo::StartDrawInfo(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms) -{ - FDrawInfo *di=di_list.GetNew(); - di->mVBO = GLRenderer->mVBO; - di->mClipper = &staticClipper; - di->Viewpoint = parentvp; - if (uniforms) - { - di->VPUniforms = *uniforms; - // The clip planes will never be inherited from the parent drawinfo. - di->VPUniforms.mClipLine.X = -1000001.f; - di->VPUniforms.mClipHeight = 0; - } - else di->VPUniforms.SetDefaults(); - di->mClipper->SetViewpoint(di->Viewpoint); - staticClipper.Clear(); - di->StartScene(); - return di; -} - -void FDrawInfo::StartScene() -{ - ClearBuffers(); - - outer = gl_drawinfo; - gl_drawinfo = this; - for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset(); - decals[0].Clear(); - decals[1].Clear(); - hudsprites.Clear(); - vpIndex = 0; - - // Fullbright information needs to be propagated from the main view. - if (outer != nullptr) FullbrightFlags = outer->FullbrightFlags; - else FullbrightFlags = 0; - -} - -//========================================================================== -// -// -// -//========================================================================== -FDrawInfo *FDrawInfo::EndDrawInfo() -{ - assert(this == gl_drawinfo); - for(int i=0;i(outer); - di_list.Release(this); - if (gl_drawinfo == nullptr) - ResetRenderDataAllocator(); - return gl_drawinfo; -} - - -//========================================================================== -// -// Flood gaps with the back side's ceiling/floor texture -// This requires a stencil because the projected plane interferes with -// the depth buffer -// -//========================================================================== - -void FDrawInfo::SetupFloodStencil(wallseg * ws) -{ - int recursion = GLRenderer->mPortalState.GetRecursion(); - - // Create stencil - glStencilFunc(GL_EQUAL, recursion, ~0); // create stencil - glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // increment stencil of valid pixels - // Use revertible color mask, to avoid stomping on anaglyph 3D state - glColorMask(0, 0, 0, 0); // don't write to the graphics buffer - gl_RenderState.EnableTexture(false); - gl_RenderState.ResetColor(); - glEnable(GL_DEPTH_TEST); - glDepthMask(true); - - gl_RenderState.Apply(); - FQuadDrawer qd; - qd.Set(0, ws->x1, ws->z1, ws->y1, 0, 0); - qd.Set(1, ws->x1, ws->z2, ws->y1, 0, 0); - qd.Set(2, ws->x2, ws->z2, ws->y2, 0, 0); - qd.Set(3, ws->x2, ws->z1, ws->y2, 0, 0); - qd.Render(GL_TRIANGLE_FAN); - - glStencilFunc(GL_EQUAL, recursion + 1, ~0); // draw sky into stencil - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // this stage doesn't modify the stencil - - glColorMask(1, 1, 1, 1); // don't write to the graphics buffer - gl_RenderState.EnableTexture(true); - glDisable(GL_DEPTH_TEST); - glDepthMask(false); -} - -void FDrawInfo::ClearFloodStencil(wallseg * ws) -{ - int recursion = GLRenderer->mPortalState.GetRecursion(); - - glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); - gl_RenderState.EnableTexture(false); - // Use revertible color mask, to avoid stomping on anaglyph 3D state - glColorMask(0, 0, 0, 0); // don't write to the graphics buffer - gl_RenderState.ResetColor(); - - gl_RenderState.Apply(); - FQuadDrawer qd; - qd.Set(0, ws->x1, ws->z1, ws->y1, 0, 0); - qd.Set(1, ws->x1, ws->z2, ws->y1, 0, 0); - qd.Set(2, ws->x2, ws->z2, ws->y2, 0, 0); - qd.Set(3, ws->x2, ws->z1, ws->y2, 0, 0); - qd.Render(GL_TRIANGLE_FAN); - - // restore old stencil op. - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_EQUAL, recursion, ~0); - gl_RenderState.EnableTexture(true); - glColorMask(1, 1, 1, 1); - glEnable(GL_DEPTH_TEST); - glDepthMask(true); -} - -//========================================================================== -// -// Draw the plane segment into the gap -// -//========================================================================== -void FDrawInfo::DrawFloodedPlane(wallseg * ws, float planez, sector_t * sec, bool ceiling) -{ - GLSectorPlane plane; - int lightlevel; - FColormap Colormap; - FMaterial * gltexture; - - plane.GetFromSector(sec, ceiling); - - gltexture=FMaterial::ValidateTexture(plane.texture, false, true); - if (!gltexture) return; - - if (isFullbrightScene()) - { - Colormap.Clear(); - lightlevel=255; - } - else - { - Colormap = sec->Colormap; - if (gltexture->tex->isFullbright()) - { - Colormap.MakeWhite(); - lightlevel=255; - } - else lightlevel=abs(ceiling? sec->GetCeilingLight() : sec->GetFloorLight()); - } - - int rel = getExtraLight(); - SetColor(lightlevel, rel, Colormap, 1.0f); - SetFog(lightlevel, rel, &Colormap, false); - gl_RenderState.SetMaterial(gltexture, CLAMP_NONE, 0, -1, false); - - float fviewx = Viewpoint.Pos.X; - float fviewy = Viewpoint.Pos.Y; - float fviewz = Viewpoint.Pos.Z; - - gl_RenderState.SetPlaneTextureRotation(&plane, gltexture); - gl_RenderState.Apply(); - - float prj_fac1 = (planez-fviewz)/(ws->z1-fviewz); - float prj_fac2 = (planez-fviewz)/(ws->z2-fviewz); - - float px1 = fviewx + prj_fac1 * (ws->x1-fviewx); - float py1 = fviewy + prj_fac1 * (ws->y1-fviewy); - - float px2 = fviewx + prj_fac2 * (ws->x1-fviewx); - float py2 = fviewy + prj_fac2 * (ws->y1-fviewy); - - float px3 = fviewx + prj_fac2 * (ws->x2-fviewx); - float py3 = fviewy + prj_fac2 * (ws->y2-fviewy); - - float px4 = fviewx + prj_fac1 * (ws->x2-fviewx); - float py4 = fviewy + prj_fac1 * (ws->y2-fviewy); - - FQuadDrawer qd; - qd.Set(0, px1, planez, py1, px1 / 64, -py1 / 64); - qd.Set(1, px2, planez, py2, px2 / 64, -py2 / 64); - qd.Set(2, px3, planez, py3, px3 / 64, -py3 / 64); - qd.Set(3, px4, planez, py4, px4 / 64, -py4 / 64); - qd.Render(GL_TRIANGLE_FAN); - - gl_RenderState.EnableTextureMatrix(false); -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::FloodUpperGap(seg_t * seg) -{ - wallseg ws; - sector_t ffake, bfake; - sector_t * fakefsector = hw_FakeFlat(seg->frontsector, &ffake, in_area, true); - sector_t * fakebsector = hw_FakeFlat(seg->backsector, &bfake, in_area, false); - - vertex_t * v1, * v2; - - // Although the plane can be sloped this code will only be called - // when the edge itself is not. - double backz = fakebsector->ceilingplane.ZatPoint(seg->v1); - double frontz = fakefsector->ceilingplane.ZatPoint(seg->v1); - - if (fakebsector->GetTexture(sector_t::ceiling)==skyflatnum) return; - if (backz < Viewpoint.Pos.Z) return; - - if (seg->sidedef == seg->linedef->sidedef[0]) - { - v1=seg->linedef->v1; - v2=seg->linedef->v2; - } - else - { - v1=seg->linedef->v2; - v2=seg->linedef->v1; - } - - ws.x1 = v1->fX(); - ws.y1 = v1->fY(); - ws.x2 = v2->fX(); - ws.y2 = v2->fY(); - - ws.z1= frontz; - ws.z2= backz; - - // Step1: Draw a stencil into the gap - SetupFloodStencil(&ws); - - // Step2: Project the ceiling plane into the gap - DrawFloodedPlane(&ws, ws.z2, fakebsector, true); - - // Step3: Delete the stencil - ClearFloodStencil(&ws); -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::FloodLowerGap(seg_t * seg) -{ - wallseg ws; - sector_t ffake, bfake; - sector_t * fakefsector = hw_FakeFlat(seg->frontsector, &ffake, in_area, true); - sector_t * fakebsector = hw_FakeFlat(seg->backsector, &bfake, in_area, false); - - vertex_t * v1, * v2; - - // Although the plane can be sloped this code will only be called - // when the edge itself is not. - double backz = fakebsector->floorplane.ZatPoint(seg->v1); - double frontz = fakefsector->floorplane.ZatPoint(seg->v1); - - - if (fakebsector->GetTexture(sector_t::floor) == skyflatnum) return; - if (fakebsector->GetPlaneTexZ(sector_t::floor) > Viewpoint.Pos.Z) return; - - if (seg->sidedef == seg->linedef->sidedef[0]) - { - v1=seg->linedef->v1; - v2=seg->linedef->v2; - } - else - { - v1=seg->linedef->v2; - v2=seg->linedef->v1; - } - - ws.x1 = v1->fX(); - ws.y1 = v1->fY(); - ws.x2 = v2->fX(); - ws.y2 = v2->fY(); - - ws.z2= frontz; - ws.z1= backz; - - // Step1: Draw a stencil into the gap - SetupFloodStencil(&ws); - - // Step2: Project the ceiling plane into the gap - DrawFloodedPlane(&ws, ws.z1, fakebsector, false); - - // Step3: Delete the stencil - ClearFloodStencil(&ws); -} - -// Same here for the dependency on the portal. -void FDrawInfo::AddSubsectorToPortal(FSectorPortalGroup *ptg, subsector_t *sub) -{ - auto portal = FindPortal(ptg); - if (!portal) - { - portal = new GLScenePortal(&GLRenderer->mPortalState, new HWSectorStackPortal(ptg)); - Portals.Push(portal); - } - auto ptl = static_cast(static_cast(portal)->mScene); - ptl->AddSubsector(sub); -} - -std::pair FDrawInfo::AllocVertices(unsigned int count) -{ - unsigned int index = -1; - auto p = GLRenderer->mVBO->Alloc(count, &index); - return std::make_pair(p, index); -} - -GLDecal *FDrawInfo::AddDecal(bool onmirror) -{ - auto decal = (GLDecal*)RenderDataAllocator.Alloc(sizeof(GLDecal)); - decals[onmirror ? 1 : 0].Push(decal); - return decal; -} - -int FDrawInfo::UploadLights(FDynLightData &data) -{ - return GLRenderer->mLights->UploadLights(data); -} - -bool FDrawInfo::SetDepthClamp(bool on) -{ - return gl_RenderState.SetDepthClamp(on); -} - - - diff --git a/src/gl/scene/gl_drawinfo.h b/src/gl/scene/gl_drawinfo.h deleted file mode 100644 index 702c3981c..000000000 --- a/src/gl/scene/gl_drawinfo.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef __GL_DRAWINFO_H -#define __GL_DRAWINFO_H - -#include "gl/renderer/gl_lightdata.h" -#include "hwrenderer/scene/hw_drawlist.h" -#include "hwrenderer/scene/hw_weapon.h" -#include "hwrenderer/scene/hw_viewpointuniforms.h" - -#ifdef _MSC_VER -#pragma warning(disable:4244) -#endif - -enum DrawListType -{ - GLDL_PLAINWALLS, - GLDL_PLAINFLATS, - GLDL_MASKEDWALLS, - GLDL_MASKEDFLATS, - GLDL_MASKEDWALLSOFS, - GLDL_MODELS, - - GLDL_TRANSLUCENT, - GLDL_TRANSLUCENTBORDER, - - GLDL_TYPES, -}; - -enum Drawpasses -{ - GLPASS_ALL, // Main pass with dynamic lights - GLPASS_LIGHTSONLY, // only collect dynamic lights - GLPASS_DECALS, // Draws a decal - GLPASS_TRANSLUCENT, // Draws translucent objects -}; - -struct FDrawInfo : public HWDrawInfo -{ - HWDrawList drawlists[GLDL_TYPES]; - TArray hudsprites; // These may just be stored by value. - TArray decals[2]; // the second slot is for mirrors which get rendered in a separate pass. - int vpIndex; - - void ApplyVPUniforms() override; - - void AddWall(GLWall *wall) override; - void AddMirrorSurface(GLWall *w) override; - GLDecal *AddDecal(bool onmirror) override; - void AddPortal(GLWall *w, int portaltype) override; - void AddFlat(GLFlat *flat, bool fog) override; - void AddSprite(GLSprite *sprite, bool translucent) override; - void AddHUDSprite(HUDSprite *huds) override; - - std::pair AllocVertices(unsigned int count) override; - int UploadLights(FDynLightData &data) override; - - void DrawDecal(GLDecal *gldecal); - void DrawDecals(); - void DrawDecalsForMirror(GLWall *wall); - - void StartScene(); - void SetupFloodStencil(wallseg * ws); - void ClearFloodStencil(wallseg * ws); - void DrawFloodedPlane(wallseg * ws, float planez, sector_t * sec, bool ceiling); - void FloodUpperGap(seg_t * seg) override; - void FloodLowerGap(seg_t * seg) override; - - // Wall drawer - void RenderWall(GLWall *wall, int textured); - void RenderFogBoundary(GLWall *wall); - void RenderMirrorSurface(GLWall *wall); - void RenderTranslucentWall(GLWall *wall); - void RenderTexturedWall(GLWall *wall, int rflags); - void DrawWall(GLWall *wall, int pass) override; - - // Flat drawer - void DrawFlat(GLFlat *flat, int pass, bool trans) override; // trans only has meaning for GLPASS_LIGHTSONLY - void DrawSkyboxSector(GLFlat *flat, int pass, bool processlights); - void DrawSubsectors(GLFlat *flat, int pass, bool processlights, bool istrans); - void ProcessLights(GLFlat *flat, bool istrans); - void DrawSubsector(GLFlat *flat, subsector_t * sub); - void SetupSubsectorLights(GLFlat *flat, int pass, subsector_t * sub, int *dli); - void SetupSectorLights(GLFlat *flat, int pass, int *dli); - - // Sprite drawer - void DrawSprite(GLSprite *sprite, int pass); - void DrawPSprite(HUDSprite *huds); - void DrawPlayerSprites(bool hudModelStep); - - void DoDrawSorted(HWDrawList *dl, SortNode * head); - void DrawSorted(int listindex); - - // These two may be moved to the API independent part of the renderer later. - void AddSubsectorToPortal(FSectorPortalGroup *portal, subsector_t *sub) override; - - void CreateScene(); - void RenderScene(int recursion); - void RenderTranslucent(); - void DrawScene(int drawmode); - void ProcessScene(bool toscreen = false); - void EndDrawScene(sector_t * viewsector); - void DrawEndScene2D(sector_t * viewsector); - bool SetDepthClamp(bool on) override; - - static FDrawInfo *StartDrawInfo(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms); - FDrawInfo *EndDrawInfo(); - - gl_subsectorrendernode * GetOtherFloorPlanes(unsigned int sector) - { - if (sectorrenderstyle == STYLE_Add && !level.lightadditivesurfaces) return; // no lights on additively blended surfaces. - gl_RenderState.ApplyLightIndex(GLRenderer->mLights->GetIndex(*dli)); - (*dli)++; - return; - } - if (flat->SetupSubsectorLights(pass, sub, lightdata)) - { - int d = GLRenderer->mLights->UploadLights(lightdata); - if (pass == GLPASS_LIGHTSONLY) - { - GLRenderer->mLights->StoreIndex(d); - } - else - { - gl_RenderState.ApplyLightIndex(d); - } - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::SetupSectorLights(GLFlat *flat, int pass, int *dli) -{ - if (dli != NULL && *dli != -1) - { - if (flat->renderstyle == STYLE_Add && !level.lightadditivesurfaces) return; // no lights on additively blended surfaces. - gl_RenderState.ApplyLightIndex(GLRenderer->mLights->GetIndex(*dli)); - (*dli)++; - return; - } - if (flat->SetupSectorLights(pass, flat->sector, lightdata)) - { - int d = GLRenderer->mLights->UploadLights(lightdata); - if (pass == GLPASS_LIGHTSONLY) - { - GLRenderer->mLights->StoreIndex(d); - } - else - { - gl_RenderState.ApplyLightIndex(d); - } - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::DrawSubsector(GLFlat *flat, subsector_t * sub) -{ - if (gl.buffermethod != BM_DEFERRED) - { - FFlatVertex *ptr = GLRenderer->mVBO->GetBuffer(); - for (unsigned int k = 0; k < sub->numlines; k++) - { - vertex_t *vt = sub->firstline[k].v1; - ptr->x = vt->fX(); - ptr->z = flat->plane.plane.ZatPoint(vt) + flat->dz; - ptr->y = vt->fY(); - ptr->u = vt->fX() / 64.f; - ptr->v = -vt->fY() / 64.f; - ptr++; - } - GLRenderer->mVBO->RenderCurrent(ptr, GL_TRIANGLE_FAN); - } - else - { - // if we cannot access the buffer, use the quad drawer as fallback by splitting the subsector into quads. - // Trying to get this into the vertex buffer in the processing pass is too costly and this is only used for render hacks. - FQuadDrawer qd; - unsigned int vi[4]; - - vi[0] = 0; - for (unsigned int i = 0; i < sub->numlines - 2; i += 2) - { - for (unsigned int j = 1; j < 4; j++) - { - vi[j] = MIN(i + j, sub->numlines - 1); - } - for (unsigned int x = 0; x < 4; x++) - { - vertex_t *vt = sub->firstline[vi[x]].v1; - qd.Set(x, vt->fX(), flat->plane.plane.ZatPoint(vt) + flat->dz, vt->fY(), vt->fX() / 64.f, -vt->fY() / 64.f); - } - qd.Render(GL_TRIANGLE_FAN); - } - } - - flatvertices += sub->numlines; - flatprimitives++; -} - - -//========================================================================== -// -// this is only used by LM_DEFERRED -// -//========================================================================== - -void FDrawInfo::ProcessLights(GLFlat *flat, bool istrans) -{ - flat->dynlightindex = GLRenderer->mLights->GetIndexPtr(); - - if (flat->sector->ibocount > 0 && !ClipLineShouldBeActive()) - { - SetupSectorLights(flat, GLPASS_LIGHTSONLY, nullptr); - } - else - { - // Draw the subsectors belonging to this sector - for (int i = 0; i < flat->sector->subsectorcount; i++) - { - subsector_t * sub = flat->sector->subsectors[i]; - if (ss_renderflags[sub->Index()] & flat->renderflags || istrans) - { - SetupSubsectorLights(flat, GLPASS_LIGHTSONLY, sub, nullptr); - } - } - } - - // Draw the subsectors assigned to it due to missing textures - if (!(flat->renderflags&SSRF_RENDER3DPLANES)) - { - gl_subsectorrendernode * node = (flat->renderflags&SSRF_RENDERFLOOR)? - GetOtherFloorPlanes(flat->sector->sectornum) : - GetOtherCeilingPlanes(flat->sector->sectornum); - - while (node) - { - SetupSubsectorLights(flat, GLPASS_LIGHTSONLY, node->sub, nullptr); - node = node->next; - } - } -} - - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::DrawSubsectors(GLFlat *flat, int pass, bool processlights, bool istrans) -{ - int dli = flat->dynlightindex; - auto vcount = flat->sector->ibocount; - - gl_RenderState.Apply(); - auto iboindex = flat->iboindex; - - if (iboindex >= 0) - { - if (vcount > 0 && !ClipLineShouldBeActive()) - { - if (processlights) SetupSectorLights(flat, GLPASS_ALL, &dli); - drawcalls.Clock(); - glDrawElements(GL_TRIANGLES, vcount, GL_UNSIGNED_INT, GLRenderer->mVBO->GetIndexPointer() + iboindex); - drawcalls.Unclock(); - flatvertices += vcount; - flatprimitives++; - } - else - { - int index = iboindex; - for (int i = 0; i < flat->sector->subsectorcount; i++) - { - subsector_t * sub = flat->sector->subsectors[i]; - if (sub->numlines <= 2) continue; - - if (ss_renderflags[sub->Index()] & flat->renderflags || istrans) - { - if (processlights) SetupSubsectorLights(flat, GLPASS_ALL, sub, &dli); - drawcalls.Clock(); - glDrawElements(GL_TRIANGLES, (sub->numlines - 2) * 3, GL_UNSIGNED_INT, GLRenderer->mVBO->GetIndexPointer() + index); - drawcalls.Unclock(); - flatvertices += sub->numlines; - flatprimitives++; - } - index += (sub->numlines - 2) * 3; - } - } - } - else - { - // Draw the subsectors belonging to this sector - for (int i=0; isector->subsectorcount; i++) - { - subsector_t * sub = flat->sector->subsectors[i]; - if (ss_renderflags[sub->Index()]& flat->renderflags || istrans) - { - if (processlights) SetupSubsectorLights(flat, GLPASS_ALL, sub, &dli); - DrawSubsector(flat, sub); - } - } - } - - // Draw the subsectors assigned to it due to missing textures - if (!(flat->renderflags&SSRF_RENDER3DPLANES)) - { - gl_subsectorrendernode * node = (flat->renderflags&SSRF_RENDERFLOOR)? - GetOtherFloorPlanes(flat->sector->sectornum) : - GetOtherCeilingPlanes(flat->sector->sectornum); - - while (node) - { - if (processlights) SetupSubsectorLights(flat, GLPASS_ALL, node->sub, &dli); - DrawSubsector(flat, node->sub); - node = node->next; - } - } -} - - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::DrawSkyboxSector(GLFlat *flat, int pass, bool processlights) -{ - FQuadDrawer qd; - flat->CreateSkyboxVertices(qd.Pointer()); - qd.Render(GL_TRIANGLE_FAN); - - flatvertices += 4; - flatprimitives++; -} - - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::DrawFlat(GLFlat *flat, int pass, bool trans) // trans only has meaning for GLPASS_LIGHTSONLY -{ - int rel = getExtraLight(); - - auto &plane = flat->plane; - auto processLights = level.HasDynamicLights && !isFullbrightScene(); - gl_RenderState.SetNormal(plane.plane.Normal().X, plane.plane.Normal().Z, plane.plane.Normal().Y); - - switch (pass) - { - case GLPASS_ALL: // Single-pass rendering - SetColor(flat->lightlevel, rel, flat->Colormap,1.0f); - SetFog(flat->lightlevel, rel, &flat->Colormap, false); - if (!flat->gltexture->tex->isFullbright()) - gl_RenderState.SetObjectColor(flat->FlatColor | 0xff000000); - if (flat->sector->special != GLSector_Skybox) - { - gl_RenderState.SetMaterial(flat->gltexture, CLAMP_NONE, 0, -1, false); - gl_RenderState.SetPlaneTextureRotation(&plane, flat->gltexture); - DrawSubsectors(flat, pass, processLights && (gl.lightmethod == LM_DIRECT || flat->dynlightindex > -1), false); - gl_RenderState.EnableTextureMatrix(false); - } - else - { - gl_RenderState.SetMaterial(flat->gltexture, CLAMP_XY, 0, -1, false); - DrawSkyboxSector(flat, pass, processLights && (gl.lightmethod == LM_DIRECT || flat->dynlightindex > -1)); - } - gl_RenderState.SetObjectColor(0xffffffff); - break; - - case GLPASS_LIGHTSONLY: - if ((!trans || flat->gltexture) && processLights) - { - ProcessLights(flat, trans); - } - break; - - case GLPASS_TRANSLUCENT: - if (flat->renderstyle==STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE); - SetColor(flat->lightlevel, rel, flat->Colormap, flat->alpha); - SetFog(flat->lightlevel, rel, &flat->Colormap, false); - if (!flat->gltexture || !flat->gltexture->tex->isFullbright()) - gl_RenderState.SetObjectColor(flat->FlatColor | 0xff000000); - if (!flat->gltexture) - { - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_RenderState.EnableTexture(false); - DrawSubsectors(flat, pass, false, true); - gl_RenderState.EnableTexture(true); - } - else - { - if (!flat->gltexture->tex->GetTranslucency()) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); - else gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_RenderState.SetMaterial(flat->gltexture, CLAMP_NONE, 0, -1, false); - gl_RenderState.SetPlaneTextureRotation(&plane, flat->gltexture); - DrawSubsectors(flat, pass, processLights && (gl.lightmethod == LM_DIRECT || flat->dynlightindex > -1), true); - gl_RenderState.EnableTextureMatrix(false); - } - if (flat->renderstyle==STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.SetObjectColor(0xffffffff); - break; - } -} - -//========================================================================== -// -// FDrawInfo::AddFlat -// -// Checks texture, lighting and translucency settings and puts this -// plane in the appropriate render list. -// -//========================================================================== - -void FDrawInfo::AddFlat(GLFlat *flat, bool fog) -{ - int list; - - if (flat->renderstyle != STYLE_Translucent || flat->alpha < 1.f - FLT_EPSILON || fog || flat->gltexture == nullptr) - { - // translucent 3D floors go into the regular translucent list, translucent portals go into the translucent border list. - list = (flat->renderflags&SSRF_RENDER3DPLANES) ? GLDL_TRANSLUCENT : GLDL_TRANSLUCENTBORDER; - } - else if (flat->gltexture->tex->GetTranslucency()) - { - if (flat->stack) - { - list = GLDL_TRANSLUCENTBORDER; - } - else if ((flat->renderflags&SSRF_RENDER3DPLANES) && !flat->plane.plane.isSlope()) - { - list = GLDL_TRANSLUCENT; - } - else - { - list = GLDL_PLAINFLATS; - } - } - else - { - bool masked = flat->gltexture->isMasked() && ((flat->renderflags&SSRF_RENDER3DPLANES) || flat->stack); - list = masked ? GLDL_MASKEDFLATS : GLDL_PLAINFLATS; - } - auto newflat = drawlists[list].NewFlat(); - *newflat = *flat; -} - diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp deleted file mode 100644 index 5820f11df..000000000 --- a/src/gl/scene/gl_portal.cpp +++ /dev/null @@ -1,513 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2004-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_portal.cpp -** Generalized portal maintenance classes for skyboxes, horizons etc. -** -*/ - -#include "gl_load/gl_system.h" -#include "p_local.h" -#include "c_dispatch.h" -#include "doomstat.h" -#include "a_sharedglobal.h" -#include "r_sky.h" -#include "p_maputl.h" -#include "d_player.h" -#include "g_levellocals.h" - -#include "gl_load/gl_interface.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/data/gl_vertexbuffer.h" -#include "hwrenderer/scene/hw_clipper.h" -#include "gl/scene/gl_portal.h" -#include "gl/data/gl_viewpointbuffer.h" - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// -// -// General portal handling code -// -// -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -EXTERN_CVAR(Bool, gl_portals) -EXTERN_CVAR(Bool, gl_noquery) -EXTERN_CVAR(Int, r_mirror_recursions) - -//========================================================================== -// -// -// -//========================================================================== -void GLPortal::ClearScreen(HWDrawInfo *di) -{ - bool multi = !!glIsEnabled(GL_MULTISAMPLE); - - GLRenderer->mViewpoints->Set2D(SCREENWIDTH, SCREENHEIGHT); - gl_RenderState.SetColor(0, 0, 0); - gl_RenderState.Apply(); - - glDisable(GL_MULTISAMPLE); - glDisable(GL_DEPTH_TEST); - - glDrawArrays(GL_TRIANGLE_STRIP, FFlatVertexBuffer::FULLSCREEN_INDEX, 4); - - glEnable(GL_DEPTH_TEST); - if (multi) glEnable(GL_MULTISAMPLE); -} - -//----------------------------------------------------------------------------- -// -// DrawPortalStencil -// -//----------------------------------------------------------------------------- -void GLPortal::DrawPortalStencil(int pass) -{ - if (mPrimIndices.Size() == 0) - { - mPrimIndices.Resize(2 * lines.Size()); - - for (unsigned int i = 0; i < lines.Size(); i++) - { - mPrimIndices[i * 2] = lines[i].vertindex; - mPrimIndices[i * 2 + 1] = lines[i].vertcount; - } - } - gl_RenderState.Apply(); - for (unsigned int i = 0; i < mPrimIndices.Size(); i += 2) - { - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, mPrimIndices[i], mPrimIndices[i + 1]); - } - if (NeedCap() && lines.Size() > 1) - { - if (pass == STP_AllInOne) glDepthMask(false); - else if (pass == STP_DepthRestore) glDepthRange(1, 1); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, FFlatVertexBuffer::STENCILTOP_INDEX, 4); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, FFlatVertexBuffer::STENCILBOTTOM_INDEX, 4); - if (pass == STP_DepthRestore) glDepthRange(0, 1); - } -} - - -//----------------------------------------------------------------------------- -// -// Start -// -//----------------------------------------------------------------------------- - -bool GLPortal::Start(bool usestencil, bool doquery, HWDrawInfo *outer_di, HWDrawInfo **pDi) -{ - *pDi = nullptr; - rendered_portals++; - Clocker c(PortalAll); - - if (usestencil) - { - if (!gl_portals) - { - return false; - } - - // Create stencil - glStencilFunc(GL_EQUAL, mState->recursion, ~0); // create stencil - glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // increment stencil of valid pixels - { - glColorMask(0,0,0,0); // don't write to the graphics buffer - gl_RenderState.SetEffect(EFF_STENCIL); - gl_RenderState.EnableTexture(false); - gl_RenderState.ResetColor(); - glDepthFunc(GL_LESS); - gl_RenderState.Apply(); - - if (NeedDepthBuffer()) - { - glDepthMask(false); // don't write to Z-buffer! - if (!NeedDepthBuffer()) doquery = false; // too much overhead and nothing to gain. - else if (gl_noquery) doquery = false; - - // Use occlusion query to avoid rendering portals that aren't visible - if (doquery) glBeginQuery(GL_SAMPLES_PASSED, GLRenderer->PortalQueryObject); - - DrawPortalStencil(STP_Stencil); - - if (doquery) glEndQuery(GL_SAMPLES_PASSED); - - // Clear Z-buffer - glStencilFunc(GL_EQUAL, mState->recursion + 1, ~0); // draw sky into stencil - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // this stage doesn't modify the stencil - glDepthMask(true); // enable z-buffer again - glDepthRange(1, 1); - glDepthFunc(GL_ALWAYS); - DrawPortalStencil(STP_DepthClear); - - // set normal drawing mode - gl_RenderState.EnableTexture(true); - glDepthFunc(GL_LESS); - glColorMask(1, 1, 1, 1); - gl_RenderState.SetEffect(EFF_NONE); - glDepthRange(0, 1); - - GLuint sampleCount = 1; - - if (doquery) glGetQueryObjectuiv(GLRenderer->PortalQueryObject, GL_QUERY_RESULT, &sampleCount); - - if (sampleCount == 0) // not visible - { - // restore default stencil op. - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_EQUAL, mState->recursion, ~0); // draw sky into stencil - return false; - } - } - else - { - // No z-buffer is needed therefore we can skip all the complicated stuff that is involved - // No occlusion queries will be done here. For these portals the overhead is far greater - // than the benefit. - // Note: We must draw the stencil with z-write enabled here because there is no second pass! - - glDepthMask(true); - DrawPortalStencil(STP_AllInOne); - glStencilFunc(GL_EQUAL, mState->recursion + 1, ~0); // draw sky into stencil - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // this stage doesn't modify the stencil - gl_RenderState.EnableTexture(true); - glColorMask(1,1,1,1); - gl_RenderState.SetEffect(EFF_NONE); - glDisable(GL_DEPTH_TEST); - glDepthMask(false); // don't write to Z-buffer! - } - } - mState->recursion++; - - - } - else - { - if (!NeedDepthBuffer()) - { - glDepthMask(false); - glDisable(GL_DEPTH_TEST); - } - } - *pDi = FDrawInfo::StartDrawInfo(outer_di->Viewpoint, &outer_di->VPUniforms); - (*pDi)->mCurrentPortal = this; - - // save viewpoint - savedvisibility = outer_di->Viewpoint.camera ? outer_di->Viewpoint.camera->renderflags & RF_MAYBEINVISIBLE : ActorRenderFlags::FromInt(0); - - return true; -} - - -//----------------------------------------------------------------------------- -// -// End -// -//----------------------------------------------------------------------------- -void GLPortal::End(HWDrawInfo *di, bool usestencil) -{ - bool needdepth = NeedDepthBuffer(); - - Clocker c(PortalAll); - - di = static_cast(di)->EndDrawInfo(); - GLRenderer->mViewpoints->Bind(static_cast(di)->vpIndex); - if (usestencil) - { - auto &vp = di->Viewpoint; - - // Restore the old view - if (vp.camera != nullptr) vp.camera->renderflags = (vp.camera->renderflags & ~RF_MAYBEINVISIBLE) | savedvisibility; - - glColorMask(0, 0, 0, 0); // no graphics - gl_RenderState.SetEffect(EFF_NONE); - gl_RenderState.ResetColor(); - gl_RenderState.EnableTexture(false); - gl_RenderState.Apply(); - - if (needdepth) - { - // first step: reset the depth buffer to max. depth - glDepthRange(1, 1); // always - glDepthFunc(GL_ALWAYS); // write the farthest depth value - DrawPortalStencil(STP_DepthClear); - } - else - { - glEnable(GL_DEPTH_TEST); - } - - // second step: restore the depth buffer to the previous values and reset the stencil - glDepthFunc(GL_LEQUAL); - glDepthRange(0, 1); - glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); - glStencilFunc(GL_EQUAL, mState->recursion, ~0); // draw sky into stencil - DrawPortalStencil(STP_DepthRestore); - glDepthFunc(GL_LESS); - - - gl_RenderState.EnableTexture(true); - gl_RenderState.SetEffect(EFF_NONE); - glColorMask(1, 1, 1, 1); - mState->recursion--; - - // restore old stencil op. - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_EQUAL, mState->recursion, ~0); // draw sky into stencil - } - else - { - if (needdepth) - { - glClear(GL_DEPTH_BUFFER_BIT); - } - else - { - glEnable(GL_DEPTH_TEST); - glDepthMask(true); - } - auto &vp = di->Viewpoint; - - // Restore the old view - if (vp.camera != nullptr) vp.camera->renderflags = (vp.camera->renderflags & ~RF_MAYBEINVISIBLE) | savedvisibility; - - // This draws a valid z-buffer into the stencil's contents to ensure it - // doesn't get overwritten by the level's geometry. - - gl_RenderState.ResetColor(); - glDepthFunc(GL_LEQUAL); - glDepthRange(0, 1); - glColorMask(0, 0, 0, 1); // mark portal in alpha channel but don't touch color - gl_RenderState.SetEffect(EFF_STENCIL); - gl_RenderState.EnableTexture(false); - gl_RenderState.BlendFunc(GL_ONE, GL_ZERO); - gl_RenderState.BlendEquation(GL_FUNC_ADD); - gl_RenderState.Apply(); - DrawPortalStencil(STP_DepthRestore); - gl_RenderState.SetEffect(EFF_NONE); - gl_RenderState.EnableTexture(true); - glColorMask(1, 1, 1, 1); // mark portal in alpha channel but don't touch color - - glDepthFunc(GL_LESS); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// -// -// Horizon Portal -// -// -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -GLHorizonPortal::GLHorizonPortal(FPortalSceneState *s, GLHorizonInfo * pt, FRenderViewpoint &vp, bool local) - : GLPortal(s, local) -{ - origin = pt; - - // create the vertex data for this horizon portal. - GLSectorPlane * sp = &origin->plane; - const float vx = vp.Pos.X; - const float vy = vp.Pos.Y; - const float vz = vp.Pos.Z; - const float z = sp->Texheight; - const float tz = (z - vz); - - // Draw to some far away boundary - // This is not drawn as larger strips because it causes visual glitches. - FFlatVertex *ptr = GLRenderer->mVBO->GetBuffer(); - for (int xx = -32768; xx < 32768; xx += 4096) - { - float x = xx + vx; - for (int yy = -32768; yy < 32768; yy += 4096) - { - float y = yy + vy; - ptr->Set(x, z, y, x / 64, -y / 64); - ptr++; - ptr->Set(x + 4096, z, y, x / 64 + 64, -y / 64); - ptr++; - ptr->Set(x, z, y + 4096, x / 64, -y / 64 - 64); - ptr++; - ptr->Set(x + 4096, z, y + 4096, x / 64 + 64, -y / 64 - 64); - ptr++; - } - } - - // fill the gap between the polygon and the true horizon - // Since I can't draw into infinity there can always be a - // small gap - ptr->Set(-32768 + vx, z, -32768 + vy, 512.f, 0); - ptr++; - ptr->Set(-32768 + vx, vz, -32768 + vy, 512.f, tz); - ptr++; - ptr->Set(-32768 + vx, z, 32768 + vy, -512.f, 0); - ptr++; - ptr->Set(-32768 + vx, vz, 32768 + vy, -512.f, tz); - ptr++; - ptr->Set(32768 + vx, z, 32768 + vy, 512.f, 0); - ptr++; - ptr->Set(32768 + vx, vz, 32768 + vy, 512.f, tz); - ptr++; - ptr->Set(32768 + vx, z, -32768 + vy, -512.f, 0); - ptr++; - ptr->Set(32768 + vx, vz, -32768 + vy, -512.f, tz); - ptr++; - ptr->Set(-32768 + vx, z, -32768 + vy, 512.f, 0); - ptr++; - ptr->Set(-32768 + vx, vz, -32768 + vy, 512.f, tz); - ptr++; - - vcount = GLRenderer->mVBO->GetCount(ptr, &voffset) - 10; - -} - -//----------------------------------------------------------------------------- -// -// GLHorizonPortal::DrawContents -// -//----------------------------------------------------------------------------- -void GLHorizonPortal::DrawContents(HWDrawInfo *hwdi) -{ - auto di = static_cast(hwdi); - Clocker c(PortalAll); - - FMaterial * gltexture; - player_t * player=&players[consoleplayer]; - GLSectorPlane * sp = &origin->plane; - auto &vp = di->Viewpoint; - - gltexture=FMaterial::ValidateTexture(sp->texture, false, true); - if (!gltexture) - { - ClearScreen(di); - return; - } - di->SetCameraPos(vp.Pos); - - - if (gltexture && gltexture->tex->isFullbright()) - { - // glowing textures are always drawn full bright without color - di->SetColor(255, 0, origin->colormap, 1.f); - di->SetFog(255, 0, &origin->colormap, false); - } - else - { - int rel = getExtraLight(); - di->SetColor(origin->lightlevel, rel, origin->colormap, 1.0f); - di->SetFog(origin->lightlevel, rel, &origin->colormap, false); - } - - - gl_RenderState.SetMaterial(gltexture, CLAMP_NONE, 0, -1, false); - gl_RenderState.SetObjectColor(origin->specialcolor); - - gl_RenderState.SetPlaneTextureRotation(sp, gltexture); - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_RenderState.BlendFunc(GL_ONE,GL_ZERO); - gl_RenderState.Apply(); - - - for (unsigned i = 0; i < vcount; i += 4) - { - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_STRIP, voffset + i, 4); - } - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_STRIP, voffset + vcount, 10); - - gl_RenderState.EnableTextureMatrix(false); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// -// -// Eternity-style horizon portal -// -// To the rest of the engine these masquerade as a skybox portal -// Internally they need to draw two horizon or sky portals -// and will use the respective classes to achieve that. -// -// -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- - -void GLEEHorizonPortal::DrawContents(HWDrawInfo *di) -{ - auto &vp = di->Viewpoint; - sector_t *sector = portal->mOrigin; - if (sector->GetTexture(sector_t::floor) == skyflatnum || - sector->GetTexture(sector_t::ceiling) == skyflatnum) - { - GLSkyInfo skyinfo; - skyinfo.init(sector->sky, 0); - GLSkyPortal sky(mState, &skyinfo, true); - sky.DrawContents(di); - } - if (sector->GetTexture(sector_t::ceiling) != skyflatnum) - { - GLHorizonInfo horz; - horz.plane.GetFromSector(sector, sector_t::ceiling); - horz.lightlevel = hw_ClampLight(sector->GetCeilingLight()); - horz.colormap = sector->Colormap; - horz.specialcolor = 0xffffffff; - if (portal->mType == PORTS_PLANE) - { - horz.plane.Texheight = vp.Pos.Z + fabs(horz.plane.Texheight); - } - GLHorizonPortal ceil(mState, &horz, di->Viewpoint, true); - ceil.DrawContents(di); - } - if (sector->GetTexture(sector_t::floor) != skyflatnum) - { - GLHorizonInfo horz; - horz.plane.GetFromSector(sector, sector_t::floor); - horz.lightlevel = hw_ClampLight(sector->GetFloorLight()); - horz.colormap = sector->Colormap; - horz.specialcolor = 0xffffffff; - if (portal->mType == PORTS_PLANE) - { - horz.plane.Texheight = vp.Pos.Z - fabs(horz.plane.Texheight); - } - GLHorizonPortal floor(mState, &horz, di->Viewpoint, true); - floor.DrawContents(di); - } -} - -const char *GLSkyPortal::GetName() { return "Sky"; } -const char *GLHorizonPortal::GetName() { return "Horizon"; } -const char *GLEEHorizonPortal::GetName() { return "EEHorizon"; } - diff --git a/src/gl/scene/gl_portal.h b/src/gl/scene/gl_portal.h deleted file mode 100644 index dd645109d..000000000 --- a/src/gl/scene/gl_portal.h +++ /dev/null @@ -1,166 +0,0 @@ -/* -** gl_renderstruct.h -** Generalized portal maintenance classes to make rendering special effects easier -** and help add future extensions -** -**--------------------------------------------------------------------------- -** Copyright 2002-2005 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#ifndef __GL_PORTAL_H -#define __GL_PORTAL_H - -#include "tarray.h" -#include "r_utility.h" -#include "actor.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/scene/gl_drawinfo.h" -#include "hwrenderer/scene/hw_drawstructs.h" -#include "hwrenderer/scene/hw_portal.h" - -struct GLEEHorizonPortal; - -class GLPortal : public IPortal -{ - -private: - - enum - { - STP_Stencil, - STP_DepthClear, - STP_DepthRestore, - STP_AllInOne - }; - void DrawPortalStencil(int pass); - - ActorRenderFlags savedvisibility; - TArray mPrimIndices; - -protected: - int level; - - GLPortal(FPortalSceneState *state, bool local = false) : IPortal(state, local) { } - - bool Start(bool usestencil, bool doquery, HWDrawInfo *outer_di, HWDrawInfo **pDi) override; - void End(HWDrawInfo *di, bool usestencil) override; - void ClearScreen(HWDrawInfo *di); -}; - -class GLScenePortal : public GLPortal -{ -public: - HWScenePortalBase *mScene; - GLScenePortal(FPortalSceneState *state, HWScenePortalBase *handler) : GLPortal(state) - { - mScene = handler; - handler->SetOwner(this); - } - ~GLScenePortal() { delete mScene; } - virtual void * GetSource() const { return mScene->GetSource(); } - virtual const char *GetName() { return mScene->GetName(); } - virtual bool IsSky() { return mScene->IsSky(); } - virtual bool NeedCap() { return true; } - virtual bool NeedDepthBuffer() { return true; } - virtual void DrawContents(HWDrawInfo *di) - { - if (mScene->Setup(di, di->mClipper)) - { - static_cast(di)->DrawScene(DM_PORTAL); - mScene->Shutdown(di); - } - else ClearScreen(di); - } - virtual void RenderAttached(HWDrawInfo *di) { return mScene->RenderAttached(di); } -}; - - -struct GLSkyPortal : public GLPortal -{ - GLSkyInfo * origin; - friend struct GLEEHorizonPortal; - -protected: - virtual void DrawContents(HWDrawInfo *di); - virtual void * GetSource() const { return origin; } - virtual bool IsSky() { return true; } - virtual bool NeedDepthBuffer() { return false; } - virtual const char *GetName(); - -public: - - - GLSkyPortal(FPortalSceneState *state, GLSkyInfo * pt, bool local = false) - : GLPortal(state, local) - { - origin=pt; - } - -}; - -struct GLHorizonPortal : public GLPortal -{ - GLHorizonInfo * origin; - unsigned int voffset; - unsigned int vcount; - friend struct GLEEHorizonPortal; - -protected: - virtual void DrawContents(HWDrawInfo *di); - virtual void * GetSource() const { return origin; } - virtual bool NeedDepthBuffer() { return false; } - virtual bool NeedCap() { return false; } - virtual const char *GetName(); - -public: - - GLHorizonPortal(FPortalSceneState *state, GLHorizonInfo * pt, FRenderViewpoint &vp, bool local = false); -}; - -struct GLEEHorizonPortal : public GLPortal -{ - FSectorPortal * portal; - -protected: - virtual void DrawContents(HWDrawInfo *di); - virtual void * GetSource() const { return portal; } - virtual bool NeedDepthBuffer() { return false; } - virtual bool NeedCap() { return false; } - virtual const char *GetName(); - -public: - - GLEEHorizonPortal(FPortalSceneState *state, FSectorPortal *pt) : GLPortal(state) - { - portal=pt; - } - -}; - -#endif diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp deleted file mode 100644 index 1a83521f1..000000000 --- a/src/gl/scene/gl_scene.cpp +++ /dev/null @@ -1,510 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2004-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_scene.cpp -** manages the rendering of the player's view -** -*/ - -#include "gl_load/gl_system.h" -#include "gi.h" -#include "m_png.h" -#include "doomstat.h" -#include "g_level.h" -#include "r_data/r_interpolate.h" -#include "r_utility.h" -#include "d_player.h" -#include "p_effect.h" -#include "sbar.h" -#include "po_man.h" -#include "p_local.h" -#include "serializer.h" -#include "g_levellocals.h" -#include "hwrenderer/dynlights/hw_dynlightdata.h" - -#include "gl/dynlights/gl_lightbuffer.h" -#include "gl_load/gl_interface.h" -#include "gl/system/gl_framebuffer.h" -#include "gl/system/gl_debug.h" -#include "hwrenderer/utility/hw_cvars.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/renderer/gl_renderbuffers.h" -#include "gl/data/gl_vertexbuffer.h" -#include "gl/data/gl_viewpointbuffer.h" -#include "hwrenderer/scene/hw_clipper.h" -#include "hwrenderer/scene/hw_portal.h" -#include "gl/scene/gl_drawinfo.h" -#include "gl/scene/gl_portal.h" -#include "hwrenderer/utility/hw_vrmodes.h" - -//========================================================================== -// -// CVARs -// -//========================================================================== -CVAR(Bool, gl_texture, true, 0) -CVAR(Bool, gl_no_skyclear, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR(Float, gl_mask_threshold, 0.5f,CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR(Float, gl_mask_sprite_threshold, 0.5f,CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR(Bool, gl_sort_textures, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -EXTERN_CVAR (Bool, cl_capfps) -EXTERN_CVAR (Bool, r_deathcamera) -EXTERN_CVAR (Float, r_visibility) -EXTERN_CVAR (Bool, r_drawvoxels) - - -void FDrawInfo::ApplyVPUniforms() -{ - VPUniforms.CalcDependencies(); - vpIndex = GLRenderer->mViewpoints->SetViewpoint(&VPUniforms); -} - - -//----------------------------------------------------------------------------- -// -// CreateScene -// -// creates the draw lists for the current scene -// -//----------------------------------------------------------------------------- - -void FDrawInfo::CreateScene() -{ - const auto &vp = Viewpoint; - angle_t a1 = FrustumAngle(); - mClipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs() + a1, vp.Angles.Yaw.BAMs() - a1); - - // reset the portal manager - GLRenderer->mPortalState.StartFrame(); - PO_LinkToSubsectors(); - - ProcessAll.Clock(); - - // clip the scene and fill the drawlists - Bsp.Clock(); - GLRenderer->mVBO->Map(); - GLRenderer->mLights->Begin(); - - // Give the DrawInfo the viewpoint in fixed point because that's what the nodes are. - viewx = FLOAT2FIXED(vp.Pos.X); - viewy = FLOAT2FIXED(vp.Pos.Y); - - validcount++; // used for processing sidedefs only once by the renderer. - - mShadowMap = &GLRenderer->mShadowMap; - - RenderBSPNode (level.HeadNode()); - PreparePlayerSprites(vp.sector, in_area); - - // Process all the sprites on the current portal's back side which touch the portal. - if (mCurrentPortal != nullptr) mCurrentPortal->RenderAttached(this); - Bsp.Unclock(); - - // And now the crappy hacks that have to be done to avoid rendering anomalies. - // These cannot be multithreaded when the time comes because all these depend - // on the global 'validcount' variable. - - HandleMissingTextures(in_area); // Missing upper/lower textures - HandleHackedSubsectors(); // open sector hacks for deep water - ProcessSectorStacks(in_area); // merge visplanes of sector stacks - GLRenderer->mLights->Finish(); - GLRenderer->mVBO->Unmap(); - - ProcessAll.Unclock(); - -} - -//----------------------------------------------------------------------------- -// -// RenderScene -// -// Draws the current draw lists for the non GLSL renderer -// -//----------------------------------------------------------------------------- - -void FDrawInfo::RenderScene(int recursion) -{ - const auto &vp = Viewpoint; - RenderAll.Clock(); - - glDepthMask(true); - if (!gl_no_skyclear) GLRenderer->mPortalState.RenderFirstSkyPortal(recursion, this); - - gl_RenderState.EnableFog(true); - gl_RenderState.BlendFunc(GL_ONE,GL_ZERO); - - if (gl_sort_textures) - { - drawlists[GLDL_PLAINWALLS].SortWalls(); - drawlists[GLDL_PLAINFLATS].SortFlats(); - drawlists[GLDL_MASKEDWALLS].SortWalls(); - drawlists[GLDL_MASKEDFLATS].SortFlats(); - drawlists[GLDL_MASKEDWALLSOFS].SortWalls(); - } - - // if we don't have a persistently mapped buffer, we have to process all the dynamic lights up front, - // so that we don't have to do repeated map/unmap calls on the buffer. - if (gl.lightmethod == LM_DEFERRED && level.HasDynamicLights && !isFullbrightScene()) - { - GLRenderer->mLights->Begin(); - drawlists[GLDL_PLAINFLATS].DrawFlats(this, GLPASS_LIGHTSONLY); - drawlists[GLDL_MASKEDFLATS].DrawFlats(this, GLPASS_LIGHTSONLY); - drawlists[GLDL_TRANSLUCENTBORDER].Draw(this, GLPASS_LIGHTSONLY); - drawlists[GLDL_TRANSLUCENT].Draw(this, GLPASS_LIGHTSONLY, true); - GLRenderer->mLights->Finish(); - } - - // Part 1: solid geometry. This is set up so that there are no transparent parts - glDepthFunc(GL_LESS); - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - glDisable(GL_POLYGON_OFFSET_FILL); - - int pass = GLPASS_ALL; - - gl_RenderState.EnableTexture(gl_texture); - gl_RenderState.EnableBrightmap(true); - drawlists[GLDL_PLAINWALLS].DrawWalls(this, pass); - drawlists[GLDL_PLAINFLATS].DrawFlats(this, pass); - - - // Part 2: masked geometry. This is set up so that only pixels with alpha>gl_mask_threshold will show - if (!gl_texture) - { - gl_RenderState.EnableTexture(true); - gl_RenderState.SetTextureMode(TM_MASK); - } - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); - drawlists[GLDL_MASKEDWALLS].DrawWalls(this, pass); - drawlists[GLDL_MASKEDFLATS].DrawFlats(this, pass); - - // Part 3: masked geometry with polygon offset. This list is empty most of the time so only waste time on it when in use. - if (drawlists[GLDL_MASKEDWALLSOFS].Size() > 0) - { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -128.0f); - drawlists[GLDL_MASKEDWALLSOFS].DrawWalls(this, pass); - glDisable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(0, 0); - } - - drawlists[GLDL_MODELS].Draw(this, pass); - - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Part 4: Draw decals (not a real pass) - glDepthFunc(GL_LEQUAL); - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -128.0f); - glDepthMask(false); - DrawDecals(); - - gl_RenderState.SetTextureMode(TM_MODULATE); - - glDepthMask(true); - - - // Push bleeding floor/ceiling textures back a little in the z-buffer - // so they don't interfere with overlapping mid textures. - glPolygonOffset(1.0f, 128.0f); - - // Part 5: flood all the gaps with the back sector's flat texture - // This will always be drawn like GLDL_PLAIN, depending on the fog settings - - glDepthMask(false); // don't write to Z-buffer! - gl_RenderState.EnableFog(true); - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_RenderState.BlendFunc(GL_ONE,GL_ZERO); - DrawUnhandledMissingTextures(); - glDepthMask(true); - - glPolygonOffset(0.0f, 0.0f); - glDisable(GL_POLYGON_OFFSET_FILL); - RenderAll.Unclock(); - -} - -//----------------------------------------------------------------------------- -// -// RenderTranslucent -// -// Draws the current draw lists for the non GLSL renderer -// -//----------------------------------------------------------------------------- - -void FDrawInfo::RenderTranslucent() -{ - RenderAll.Clock(); - - // final pass: translucent stuff - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold); - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - gl_RenderState.EnableBrightmap(true); - drawlists[GLDL_TRANSLUCENTBORDER].Draw(this, GLPASS_TRANSLUCENT); - glDepthMask(false); - DrawSorted(GLDL_TRANSLUCENT); - gl_RenderState.EnableBrightmap(false); - - - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.5f); - glDepthMask(true); - - RenderAll.Unclock(); -} - - -//----------------------------------------------------------------------------- -// -// gl_drawscene - this function renders the scene from the current -// viewpoint, including mirrors and skyboxes and other portals -// It is assumed that the GLPortal::EndFrame returns with the -// stencil, z-buffer and the projection matrix intact! -// -//----------------------------------------------------------------------------- - -void FDrawInfo::DrawScene(int drawmode) -{ - static int recursion=0; - static int ssao_portals_available = 0; - const auto &vp = Viewpoint; - - bool applySSAO = false; - if (drawmode == DM_MAINVIEW) - { - ssao_portals_available = gl_ssao_portals; - applySSAO = true; - } - else if (drawmode == DM_OFFSCREEN) - { - ssao_portals_available = 0; - } - else if (drawmode == DM_PORTAL && ssao_portals_available > 0) - { - applySSAO = true; - ssao_portals_available--; - } - - if (vp.camera != nullptr) - { - ActorRenderFlags savedflags = vp.camera->renderflags; - CreateScene(); - vp.camera->renderflags = savedflags; - } - else - { - CreateScene(); - } - - RenderScene(recursion); - - if (applySSAO && gl_RenderState.GetPassType() == GBUFFER_PASS) - { - gl_RenderState.EnableDrawBuffers(1); - GLRenderer->AmbientOccludeScene(VPUniforms.mProjectionMatrix.get()[5]); - glViewport(screen->mSceneViewport.left, screen->mSceneViewport.top, screen->mSceneViewport.width, screen->mSceneViewport.height); - GLRenderer->mBuffers->BindSceneFB(true); - gl_RenderState.EnableDrawBuffers(gl_RenderState.GetPassDrawBufferCount()); - gl_RenderState.Apply(); - GLRenderer->mViewpoints->Bind(vpIndex); - } - - // Handle all portals after rendering the opaque objects but before - // doing all translucent stuff - recursion++; - GLRenderer->mPortalState.EndFrame(this); - recursion--; - RenderTranslucent(); -} - -//----------------------------------------------------------------------------- -// -// Draws player sprites and color blend -// -//----------------------------------------------------------------------------- - - -void FDrawInfo::EndDrawScene(sector_t * viewsector) -{ - gl_RenderState.EnableFog(false); - - // [BB] HUD models need to be rendered here. - const bool renderHUDModel = IsHUDModelForPlayerAvailable( players[consoleplayer].camera->player ); - if ( renderHUDModel ) - { - // [BB] The HUD model should be drawn over everything else already drawn. - glClear(GL_DEPTH_BUFFER_BIT); - DrawPlayerSprites(true); - } - - glDisable(GL_STENCIL_TEST); - glViewport(screen->mScreenViewport.left, screen->mScreenViewport.top, screen->mScreenViewport.width, screen->mScreenViewport.height); - - // Restore standard rendering state - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.ResetColor(); - gl_RenderState.EnableTexture(true); - glDisable(GL_SCISSOR_TEST); -} - -void FDrawInfo::DrawEndScene2D(sector_t * viewsector) -{ - const bool renderHUDModel = IsHUDModelForPlayerAvailable(players[consoleplayer].camera->player); - auto vrmode = VRMode::GetVRMode(true); - - HWViewpointUniforms vp = VPUniforms; - vp.mViewMatrix.loadIdentity(); - vp.mProjectionMatrix = vrmode->GetHUDSpriteProjection(); - GLRenderer->mViewpoints->SetViewpoint(&vp); - glDisable(GL_DEPTH_TEST); - glDisable(GL_MULTISAMPLE); - - - DrawPlayerSprites(false); - - gl_RenderState.SetSoftLightLevel(-1); - - // Restore standard rendering state - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.ResetColor(); - gl_RenderState.EnableTexture(true); - glDisable(GL_SCISSOR_TEST); -} - -//----------------------------------------------------------------------------- -// -// R_RenderView - renders one view - either the screen or a camera texture -// -//----------------------------------------------------------------------------- - -void FDrawInfo::ProcessScene(bool toscreen) -{ - iter_dlightf = iter_dlight = draw_dlight = draw_dlightf = 0; - GLRenderer->mPortalState.BeginScene(); - - int mapsection = R_PointInSubsector(Viewpoint.Pos)->mapsection; - CurrentMapSections.Set(mapsection); - DrawScene(toscreen ? DM_MAINVIEW : DM_OFFSCREEN); - -} - -//----------------------------------------------------------------------------- -// -// sets 3D viewport and initial state -// -//----------------------------------------------------------------------------- - -void FGLRenderer::Set3DViewport() -{ - // Always clear all buffers with scissor test disabled. - // This is faster on newer hardware because it allows the GPU to skip - // reading from slower memory where the full buffers are stored. - glDisable(GL_SCISSOR_TEST); - glClearColor(mSceneClearColor[0], mSceneClearColor[1], mSceneClearColor[2], 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - const auto &bounds = screen->mSceneViewport; - glViewport(bounds.left, bounds.top, bounds.width, bounds.height); - glScissor(bounds.left, bounds.top, bounds.width, bounds.height); - - glEnable(GL_SCISSOR_TEST); - - glEnable(GL_MULTISAMPLE); - glEnable(GL_DEPTH_TEST); - glEnable(GL_STENCIL_TEST); - glStencilFunc(GL_ALWAYS,0,~0); // default stencil - glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); -} - -//----------------------------------------------------------------------------- -// -// Renders one viewpoint in a scene -// -//----------------------------------------------------------------------------- - -sector_t * FGLRenderer::RenderViewpoint (FRenderViewpoint &mainvp, AActor * camera, IntRect * bounds, float fov, float ratio, float fovratio, bool mainview, bool toscreen) -{ - R_SetupFrame (mainvp, r_viewwindow, camera); - - // Render (potentially) multiple views for stereo 3d - // Fixme. The view offsetting should be done with a static table and not require setup of the entire render state for the mode. - auto vrmode = VRMode::GetVRMode(mainview && toscreen); - for (int eye_ix = 0; eye_ix < vrmode->mEyeCount; ++eye_ix) - { - const auto &eye = vrmode->mEyes[eye_ix]; - screen->SetViewportRects(bounds); - - if (mainview) // Bind the scene frame buffer and turn on draw buffers used by ssao - { - FGLDebug::PushGroup("MainView"); - - bool useSSAO = (gl_ssao != 0); - mBuffers->BindSceneFB(useSSAO); - gl_RenderState.SetPassType(useSSAO ? GBUFFER_PASS : NORMAL_PASS); - gl_RenderState.EnableDrawBuffers(gl_RenderState.GetPassDrawBufferCount()); - gl_RenderState.Apply(); - } - - Set3DViewport(); - - FDrawInfo *di = FDrawInfo::StartDrawInfo(mainvp, nullptr); - auto &vp = di->Viewpoint; - di->SetViewArea(); - auto cm = di->SetFullbrightFlags(mainview ? vp.camera->player : nullptr); - di->Viewpoint.FieldOfView = fov; // Set the real FOV for the current scene (it's not necessarily the same as the global setting in r_viewpoint) - - // Stereo mode specific perspective projection - di->VPUniforms.mProjectionMatrix = eye.GetProjection(fov, ratio, fovratio); - // Stereo mode specific viewpoint adjustment - vp.Pos += eye.GetViewShift(vp.HWAngles.Yaw.Degrees); - di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, false, false); - - di->ProcessScene(toscreen); - - if (mainview) - { - PostProcess.Clock(); - if (toscreen) di->EndDrawScene(mainvp.sector); // do not call this for camera textures. - - if (gl_RenderState.GetPassType() == GBUFFER_PASS) // Turn off ssao draw buffers - { - gl_RenderState.SetPassType(NORMAL_PASS); - gl_RenderState.EnableDrawBuffers(1); - } - - mBuffers->BlitSceneToTexture(); // Copy the resulting scene to the current post process texture - - FGLDebug::PopGroup(); // MainView - - PostProcessScene(cm, [&]() { di->DrawEndScene2D(mainvp.sector); }); - PostProcess.Unclock(); - } - di->EndDrawInfo(); - if (vrmode->mEyeCount > 1) - mBuffers->BlitToEyeTexture(eye_ix); - } - - interpolator.RestoreInterpolations (); - return mainvp.sector; -} - diff --git a/src/gl/scene/gl_skydome.cpp b/src/gl/scene/gl_skydome.cpp deleted file mode 100644 index afc4d16dd..000000000 --- a/src/gl/scene/gl_skydome.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2003-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// - -#include "gl_load/gl_system.h" -#include "doomtype.h" -#include "g_level.h" -#include "w_wad.h" -#include "r_state.h" -#include "r_utility.h" -#include "g_levellocals.h" -#include "textures/skyboxtexture.h" - -#include "gl_load/gl_interface.h" -#include "gl/data/gl_vertexbuffer.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/scene/gl_drawinfo.h" -#include "gl/scene/gl_portal.h" -#include "gl/shaders/gl_shader.h" - - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- - -FSkyVertexBuffer::FSkyVertexBuffer() -{ - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glBufferData(GL_ARRAY_BUFFER, mVertices.Size() * sizeof(FSkyVertex), &mVertices[0], GL_STATIC_DRAW); -} - -void FSkyVertexBuffer::BindVBO() -{ - glBindBuffer(GL_ARRAY_BUFFER, vbo_id); - glVertexAttribPointer(VATTR_VERTEX, 3, GL_FLOAT, false, sizeof(FSkyVertex), &VSO->x); - glVertexAttribPointer(VATTR_TEXCOORD, 2, GL_FLOAT, false, sizeof(FSkyVertex), &VSO->u); - glVertexAttribPointer(VATTR_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof(FSkyVertex), &VSO->color); - glEnableVertexAttribArray(VATTR_VERTEX); - glEnableVertexAttribArray(VATTR_TEXCOORD); - glEnableVertexAttribArray(VATTR_COLOR); - glDisableVertexAttribArray(VATTR_VERTEX2); - glDisableVertexAttribArray(VATTR_NORMAL); -} - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- - -inline void FSkyVertexBuffer::RenderRow(int prim, int row) -{ - glDrawArrays(prim, mPrimStart[row], mPrimStart[row + 1] - mPrimStart[row]); -} - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- - -void FSkyVertexBuffer::RenderDome(FMaterial *tex, int mode) -{ - int rc = mRows + 1; - - // The caps only get drawn for the main layer but not for the overlay. - if (mode == SKYMODE_MAINLAYER && tex != NULL) - { - PalEntry pe = tex->tex->GetSkyCapColor(false); - gl_RenderState.SetObjectColor(pe); - gl_RenderState.EnableTexture(false); - gl_RenderState.Apply(); - RenderRow(GL_TRIANGLE_FAN, 0); - - pe = tex->tex->GetSkyCapColor(true); - gl_RenderState.SetObjectColor(pe); - gl_RenderState.Apply(); - RenderRow(GL_TRIANGLE_FAN, rc); - gl_RenderState.EnableTexture(true); - } - gl_RenderState.SetObjectColor(0xffffffff); - gl_RenderState.Apply(); - for (int i = 1; i <= mRows; i++) - { - RenderRow(GL_TRIANGLE_STRIP, i); - RenderRow(GL_TRIANGLE_STRIP, rc + i); - } -} - - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- - -void RenderDome(FMaterial * tex, float x_offset, float y_offset, bool mirror, int mode) -{ - if (tex) - { - gl_RenderState.SetMaterial(tex, CLAMP_NONE, 0, -1, false); - gl_RenderState.EnableModelMatrix(true); - gl_RenderState.EnableTextureMatrix(true); - - GLRenderer->mSkyVBO->SetupMatrices(tex, x_offset, y_offset, mirror, mode, gl_RenderState.mModelMatrix, gl_RenderState.mTextureMatrix); - } - - GLRenderer->mSkyVBO->RenderDome(tex, mode); - gl_RenderState.EnableTextureMatrix(false); - gl_RenderState.EnableModelMatrix(false); -} - - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- - -static void RenderBox(FTextureID texno, FMaterial * gltex, float x_offset, bool sky2) -{ - FSkyBox * sb = static_cast(gltex->tex); - int faces; - FMaterial * tex; - - gl_RenderState.EnableModelMatrix(true); - gl_RenderState.mModelMatrix.loadIdentity(); - - if (!sky2) - gl_RenderState.mModelMatrix.rotate(-180.0f+x_offset, level.info->skyrotatevector.X, level.info->skyrotatevector.Z, level.info->skyrotatevector.Y); - else - gl_RenderState.mModelMatrix.rotate(-180.0f+x_offset, level.info->skyrotatevector2.X, level.info->skyrotatevector2.Z, level.info->skyrotatevector2.Y); - - if (sb->faces[5]) - { - faces=4; - - // north - tex = FMaterial::ValidateTexture(sb->faces[0], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(0), 4); - - // east - tex = FMaterial::ValidateTexture(sb->faces[1], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(1), 4); - - // south - tex = FMaterial::ValidateTexture(sb->faces[2], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(2), 4); - - // west - tex = FMaterial::ValidateTexture(sb->faces[3], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(3), 4); - } - else - { - faces=1; - tex = FMaterial::ValidateTexture(sb->faces[0], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(-1), 10); - } - - // top - tex = FMaterial::ValidateTexture(sb->faces[faces], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(sb->fliptop? 6:5), 4); - - // bottom - tex = FMaterial::ValidateTexture(sb->faces[faces+1], false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, 0, -1, false); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLE_STRIP, GLRenderer->mSkyVBO->FaceStart(4), 4); - - gl_RenderState.EnableModelMatrix(false); -} - -//----------------------------------------------------------------------------- -// -// -// -//----------------------------------------------------------------------------- -void GLSkyPortal::DrawContents(HWDrawInfo *di) -{ - bool drawBoth = false; - auto &vp = di->Viewpoint; - - // We have no use for Doom lighting special handling here, so disable it for this function. - int oldlightmode = ::level.lightmode; - if (::level.lightmode == 8) - { - ::level.lightmode = 2; - gl_RenderState.SetSoftLightLevel(-1); - } - - - gl_RenderState.ResetColor(); - gl_RenderState.EnableFog(false); - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - bool oldClamp = gl_RenderState.SetDepthClamp(true); - - di->SetupView(0, 0, 0, !!(mState->MirrorFlag & 1), !!(mState->PlaneMirrorFlag & 1)); - - gl_RenderState.SetVertexBuffer(GLRenderer->mSkyVBO); - if (origin->texture[0] && origin->texture[0]->tex->bSkybox) - { - RenderBox(origin->skytexno1, origin->texture[0], origin->x_offset[0], origin->sky2); - } - else - { - if (origin->texture[0]==origin->texture[1] && origin->doublesky) origin->doublesky=false; - - if (origin->texture[0]) - { - gl_RenderState.SetTextureMode(TM_OPAQUE); - RenderDome(origin->texture[0], origin->x_offset[0], origin->y_offset, origin->mirrored, FSkyVertexBuffer::SKYMODE_MAINLAYER); - gl_RenderState.SetTextureMode(TM_MODULATE); - } - - gl_RenderState.AlphaFunc(GL_GREATER, 0.f); - - if (origin->doublesky && origin->texture[1]) - { - RenderDome(origin->texture[1], origin->x_offset[1], origin->y_offset, false, FSkyVertexBuffer::SKYMODE_SECONDLAYER); - } - - if (::level.skyfog>0 && !di->isFullbrightScene() && (origin->fadecolor & 0xffffff) != 0) - { - PalEntry FadeColor = origin->fadecolor; - FadeColor.a = clamp(::level.skyfog, 0, 255); - - gl_RenderState.EnableTexture(false); - gl_RenderState.SetObjectColor(FadeColor); - gl_RenderState.Apply(); - glDrawArrays(GL_TRIANGLES, 0, 12); - gl_RenderState.EnableTexture(true); - gl_RenderState.SetObjectColor(0xffffffff); - } - } - gl_RenderState.SetVertexBuffer(GLRenderer->mVBO); - ::level.lightmode = oldlightmode; - gl_RenderState.SetDepthClamp(oldClamp); -} - diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp deleted file mode 100644 index 985286a2a..000000000 --- a/src/gl/scene/gl_sprite.cpp +++ /dev/null @@ -1,335 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2002-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_sprite.cpp -** Sprite/Particle rendering -** -*/ - -#include "gl_load/gl_system.h" -#include "p_local.h" -#include "p_effect.h" -#include "g_level.h" -#include "doomstat.h" -#include "r_defs.h" -#include "r_sky.h" -#include "r_utility.h" -#include "a_pickups.h" -#include "d_player.h" -#include "g_levellocals.h" -#include "events.h" -#include "actorinlines.h" -#include "r_data/r_vanillatrans.h" - -#include "gl_load/gl_interface.h" -#include "hwrenderer/utility/hw_cvars.h" -#include "hwrenderer/scene/hw_drawstructs.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/scene/gl_drawinfo.h" -#include "gl/models/gl_models.h" -#include "gl/renderer/gl_quaddrawer.h" -#include "gl/dynlights/gl_lightbuffer.h" - -extern uint32_t r_renderercaps; - -void gl_SetRenderStyle(FRenderStyle style, bool drawopaque, bool allowcolorblending) -{ - int tm, sb, db, be; - - gl_GetRenderStyle(style, drawopaque, allowcolorblending, &tm, &sb, &db, &be); - gl_RenderState.BlendEquation(be); - gl_RenderState.BlendFunc(sb, db); - gl_RenderState.SetTextureMode(tm); -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::DrawSprite(GLSprite *sprite, int pass) -{ - if (pass == GLPASS_DECALS || pass == GLPASS_LIGHTSONLY) return; - - auto RenderStyle = sprite->RenderStyle; - - bool additivefog = false; - bool foglayer = false; - int rel = sprite->fullbright? 0 : getExtraLight(); - auto &vp = Viewpoint; - - if (pass==GLPASS_TRANSLUCENT) - { - // The translucent pass requires special setup for the various modes. - - // for special render styles brightmaps would not look good - especially for subtractive. - if (RenderStyle.BlendOp != STYLEOP_Add) - { - gl_RenderState.EnableBrightmap(false); - } - - gl_SetRenderStyle(RenderStyle, false, - // The rest of the needed checks are done inside gl_SetRenderStyle - sprite->trans > 1.f - FLT_EPSILON && gl_usecolorblending && !isFullbrightScene() && sprite->actor && - sprite->fullbright && sprite->gltexture && !sprite->gltexture->tex->GetTranslucency()); - - if (sprite->hw_styleflags == STYLEHW_NoAlphaTest) - { - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - } - else - { - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold); - } - - if (RenderStyle.BlendOp == STYLEOP_Shadow) - { - float fuzzalpha=0.44f; - float minalpha=0.1f; - - // fog + fuzz don't work well without some fiddling with the alpha value! - if (!sprite->Colormap.FadeColor.isBlack()) - { - float dist=Dist2(vp.Pos.X, vp.Pos.Y, sprite->x, sprite->y); - int fogd = hw_GetFogDensity(sprite->lightlevel, sprite->Colormap.FadeColor, sprite->Colormap.FogDensity, sprite->Colormap.BlendFactor); - - // this value was determined by trial and error and is scale dependent! - float factor = 0.05f + exp(-fogd*dist / 62500.f); - fuzzalpha*=factor; - minalpha*=factor; - } - - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold); - gl_RenderState.SetColor(0.2f,0.2f,0.2f,fuzzalpha, sprite->Colormap.Desaturation); - additivefog = true; - sprite->lightlist = nullptr; // the fuzz effect does not use the sector's light level so splitting is not needed. - } - else if (RenderStyle.BlendOp == STYLEOP_Add && RenderStyle.DestAlpha == STYLEALPHA_One) - { - additivefog = true; - } - } - else if (sprite->modelframe == nullptr) - { - int tm, sb, db, be; - - // This still needs to set the texture mode. As blend mode it will always use GL_ONE/GL_ZERO - gl_GetRenderStyle(RenderStyle, false, false, &tm, &sb, &db, &be); - gl_RenderState.SetTextureMode(tm); - - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -128.0f); - } - if (RenderStyle.BlendOp != STYLEOP_Shadow) - { - if (level.HasDynamicLights && !isFullbrightScene() && !sprite->fullbright) - { - if ( sprite->dynlightindex == -1) // only set if we got no light buffer index. This covers all cases where sprite lighting is used. - { - float out[3]; - GetDynSpriteLight(gl_light_sprites ? sprite->actor : nullptr, gl_light_particles ? sprite->particle : nullptr, out); - gl_RenderState.SetDynLight(out[0], out[1], out[2]); - } - } - sector_t *cursec = sprite->actor ? sprite->actor->Sector : sprite->particle ? sprite->particle->subsector->sector : nullptr; - if (cursec != nullptr) - { - const PalEntry finalcol = sprite->fullbright - ? sprite->ThingColor - : sprite->ThingColor.Modulate(cursec->SpecialColors[sector_t::sprites]); - - gl_RenderState.SetObjectColor(finalcol); - } - SetColor(sprite->lightlevel, rel, sprite->Colormap, sprite->trans); - } - - - if (sprite->Colormap.FadeColor.isBlack()) sprite->foglevel = sprite->lightlevel; - - if (RenderStyle.Flags & STYLEF_FadeToBlack) - { - sprite->Colormap.FadeColor=0; - additivefog = true; - } - - if (RenderStyle.BlendOp == STYLEOP_RevSub || RenderStyle.BlendOp == STYLEOP_Sub) - { - if (!sprite->modelframe) - { - // non-black fog with subtractive style needs special treatment - if (!sprite->Colormap.FadeColor.isBlack()) - { - foglayer = true; - // Due to the two-layer approach we need to force an alpha test that lets everything pass - gl_RenderState.AlphaFunc(GL_GREATER, 0); - } - } - else RenderStyle.BlendOp = STYLEOP_Fuzz; // subtractive with models is not going to work. - } - - if (!foglayer) SetFog(sprite->foglevel, rel, &sprite->Colormap, additivefog); - else - { - gl_RenderState.EnableFog(false); - gl_RenderState.SetFog(0, 0); - } - - if (sprite->gltexture) gl_RenderState.SetMaterial(sprite->gltexture, CLAMP_XY, sprite->translation, sprite->OverrideShader, !!(RenderStyle.Flags & STYLEF_RedIsAlpha)); - else if (!sprite->modelframe) gl_RenderState.EnableTexture(false); - - //SetColor(lightlevel, rel, Colormap, trans); - - unsigned int iter = sprite->lightlist? sprite->lightlist->Size() : 1; - bool clipping = false; - auto lightlist = sprite->lightlist; - if (lightlist || sprite->topclip != LARGE_VALUE || sprite->bottomclip != -LARGE_VALUE) - { - clipping = true; - gl_RenderState.EnableSplit(true); - } - - secplane_t bottomp = { { 0, 0, -1. }, sprite->bottomclip }; - secplane_t topp = { { 0, 0, -1. }, sprite->topclip }; - for (unsigned i = 0; i < iter; i++) - { - if (lightlist) - { - // set up the light slice - secplane_t *topplane = i == 0 ? &topp : &(*lightlist)[i].plane; - secplane_t *lowplane = i == (*lightlist).Size() - 1 ? &bottomp : &(*lightlist)[i + 1].plane; - - int thislight = (*lightlist)[i].caster != nullptr ? hw_ClampLight(*(*lightlist)[i].p_lightlevel) : sprite->lightlevel; - int thisll = sprite->actor == nullptr? thislight : (uint8_t)sprite->actor->Sector->CheckSpriteGlow(thislight, sprite->actor->InterpolatedPosition(vp.TicFrac)); - - FColormap thiscm; - thiscm.CopyFog(sprite->Colormap); - thiscm.CopyFrom3DLight(&(*lightlist)[i]); - if (level.flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) - { - thiscm.Decolorize(); - } - - SetColor(thisll, rel, thiscm, sprite->trans); - if (!foglayer) - { - SetFog(thislight, rel, &thiscm, additivefog); - } - gl_RenderState.SetSplitPlanes(*topplane, *lowplane); - } - else if (clipping) - { - gl_RenderState.SetSplitPlanes(topp, bottomp); - } - - if (!sprite->modelframe) - { - gl_RenderState.Apply(); - - FVector3 v[4]; - gl_RenderState.SetNormal(0, 0, 0); - - if (sprite->CalculateVertices(this, v, &vp.Pos)) - { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -128.0f); - } - - FQuadDrawer qd; - qd.Set(0, v[0][0], v[0][1], v[0][2], sprite->ul, sprite->vt); - qd.Set(1, v[1][0], v[1][1], v[1][2], sprite->ur, sprite->vt); - qd.Set(2, v[2][0], v[2][1], v[2][2], sprite->ul, sprite->vb); - qd.Set(3, v[3][0], v[3][1], v[3][2], sprite->ur, sprite->vb); - qd.Render(GL_TRIANGLE_STRIP); - - if (foglayer) - { - // If we get here we know that we have colored fog and no fixed colormap. - SetFog(sprite->foglevel, rel, &sprite->Colormap, additivefog); - gl_RenderState.SetTextureMode(TM_FOGLAYER); - gl_RenderState.BlendEquation(GL_FUNC_ADD); - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.Apply(); - qd.Render(GL_TRIANGLE_STRIP); - gl_RenderState.SetTextureMode(TM_MODULATE); - } - } - else - { - FGLModelRenderer renderer(this, sprite->dynlightindex); - renderer.RenderModel(sprite->x, sprite->y, sprite->z, sprite->modelframe, sprite->actor, vp.TicFrac); - } - } - - if (clipping) - { - gl_RenderState.EnableSplit(false); - } - - if (pass==GLPASS_TRANSLUCENT) - { - gl_RenderState.EnableBrightmap(true); - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.BlendEquation(GL_FUNC_ADD); - gl_RenderState.SetTextureMode(TM_MODULATE); - if (sprite->actor != nullptr && (sprite->actor->renderflags & RF_SPRITETYPEMASK) == RF_FLATSPRITE) - { - glPolygonOffset(0.0f, 0.0f); - glDisable(GL_POLYGON_OFFSET_FILL); - } - } - else if (sprite->modelframe == nullptr) - { - glPolygonOffset(0.0f, 0.0f); - glDisable(GL_POLYGON_OFFSET_FILL); - } - - gl_RenderState.SetObjectColor(0xffffffff); - gl_RenderState.EnableTexture(true); - gl_RenderState.SetDynLight(0,0,0); -} - - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::AddSprite(GLSprite *sprite, bool translucent) -{ - int list; - // [BB] Allow models to be drawn in the GLDL_TRANSLUCENT pass. - if (translucent || sprite->actor == nullptr || (!sprite->modelframe && (sprite->actor->renderflags & RF_SPRITETYPEMASK) != RF_WALLSPRITE)) - { - list = GLDL_TRANSLUCENT; - } - else - { - list = GLDL_MODELS; - } - - auto newsprt = drawlists[list].NewSprite(); - *newsprt = *sprite; -} - diff --git a/src/gl/scene/gl_walls_draw.cpp b/src/gl/scene/gl_walls_draw.cpp deleted file mode 100644 index 61f51a1d5..000000000 --- a/src/gl/scene/gl_walls_draw.cpp +++ /dev/null @@ -1,578 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2000-2016 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// - -#include "gl_load/gl_system.h" -#include "p_local.h" -#include "p_lnspec.h" -#include "a_sharedglobal.h" -#include "g_levellocals.h" -#include "actorinlines.h" -#include "hwrenderer/dynlights/hw_dynlightdata.h" - -#include "gl_load/gl_interface.h" -#include "hwrenderer/utility/hw_cvars.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/data/gl_vertexbuffer.h" -#include "gl/dynlights/gl_lightbuffer.h" -#include "gl/scene/gl_drawinfo.h" -#include "gl/scene/gl_portal.h" - -EXTERN_CVAR(Bool, gl_seamless) - -//========================================================================== -// -// General purpose wall rendering function -// everything goes through here -// -//========================================================================== - -void FDrawInfo::RenderWall(GLWall *wall, int textured) -{ - assert(wall->vertcount > 0); - gl_RenderState.Apply(); - gl_RenderState.ApplyLightIndex(wall->dynlightindex); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, wall->vertindex, wall->vertcount); - vertexcount += wall->vertcount; -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::RenderFogBoundary(GLWall *wall) -{ - if (gl_fogmode && !isFullbrightScene()) - { - int rel = wall->rellight + getExtraLight(); - SetFog(wall->lightlevel, rel, &wall->Colormap, false); - gl_RenderState.EnableDrawBuffers(1); - gl_RenderState.SetEffect(EFF_FOGBOUNDARY); - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -128.0f); - RenderWall(wall, GLWall::RWF_BLANK); - glPolygonOffset(0.0f, 0.0f); - glDisable(GL_POLYGON_OFFSET_FILL); - gl_RenderState.SetEffect(EFF_NONE); - gl_RenderState.EnableDrawBuffers(gl_RenderState.GetPassDrawBufferCount()); - } -} - - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::RenderMirrorSurface(GLWall *wall) -{ - if (!TexMan.mirrorTexture.isValid()) return; - - // we use texture coordinates and texture matrix to pass the normal stuff to the shader so that the default vertex buffer format can be used as is. - gl_RenderState.EnableTextureMatrix(true); - - // Use sphere mapping for this - gl_RenderState.SetEffect(EFF_SPHEREMAP); - - SetColor(wall->lightlevel, 0, wall->Colormap ,0.1f); - SetFog(wall->lightlevel, 0, &wall->Colormap, true); - gl_RenderState.BlendFunc(GL_SRC_ALPHA,GL_ONE); - gl_RenderState.AlphaFunc(GL_GREATER,0); - glDepthFunc(GL_LEQUAL); - - FMaterial * pat=FMaterial::ValidateTexture(TexMan.mirrorTexture, false, false); - gl_RenderState.SetMaterial(pat, CLAMP_NONE, 0, -1, false); - - wall->flags &= ~GLWall::GLWF_GLOW; - RenderWall(wall, GLWall::RWF_BLANK); - - gl_RenderState.EnableTextureMatrix(false); - gl_RenderState.SetEffect(EFF_NONE); - - // Restore the defaults for the translucent pass - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold); - glDepthFunc(GL_LESS); - - // This is drawn in the translucent pass which is done after the decal pass - // As a result the decals have to be drawn here, right after the wall they are on, - // because the depth buffer won't get set by translucent items. - if (wall->seg->sidedef->AttachedDecals) - { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -128.0f); - glDepthMask(false); - DrawDecalsForMirror(wall); - glDepthMask(true); - glPolygonOffset(0.0f, 0.0f); - glDisable(GL_POLYGON_OFFSET_FILL); - gl_RenderState.SetTextureMode(TM_MODULATE); - gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::RenderTexturedWall(GLWall *wall, int rflags) -{ - int tmode = gl_RenderState.GetTextureMode(); - int rel = wall->rellight + getExtraLight(); - - if (wall->flags & GLWall::GLWF_GLOW) - { - gl_RenderState.EnableGlow(true); - gl_RenderState.SetGlowParams(wall->topglowcolor, wall->bottomglowcolor); - } - gl_RenderState.SetGlowPlanes(wall->topplane, wall->bottomplane); - gl_RenderState.SetMaterial(wall->gltexture, wall->flags & 3, 0, -1, false); - - if (wall->type == RENDERWALL_M2SNF) - { - if (wall->flags & GLWall::GLWF_CLAMPY) - { - if (tmode == TM_MODULATE) gl_RenderState.SetTextureMode(TM_CLAMPY); - } - SetFog(255, 0, nullptr, false); - } - gl_RenderState.SetObjectColor(wall->seg->frontsector->SpecialColors[sector_t::walltop] | 0xff000000); - gl_RenderState.SetObjectColor2(wall->seg->frontsector->SpecialColors[sector_t::wallbottom] | 0xff000000); - - float absalpha = fabsf(wall->alpha); - if (wall->lightlist == nullptr) - { - if (wall->type != RENDERWALL_M2SNF) SetFog(wall->lightlevel, rel, &wall->Colormap, wall->RenderStyle == STYLE_Add); - SetColor(wall->lightlevel, rel, wall->Colormap, absalpha); - RenderWall(wall, rflags); - } - else - { - gl_RenderState.EnableSplit(true); - - for (unsigned i = 0; i < wall->lightlist->Size(); i++) - { - secplane_t &lowplane = i == (*wall->lightlist).Size() - 1 ? wall->bottomplane : (*wall->lightlist)[i + 1].plane; - // this must use the exact same calculation method as GLWall::Process etc. - float low1 = lowplane.ZatPoint(wall->vertexes[0]); - float low2 = lowplane.ZatPoint(wall->vertexes[1]); - - if (low1 < wall->ztop[0] || low2 < wall->ztop[1]) - { - int thisll = (*wall->lightlist)[i].caster != NULL ? hw_ClampLight(*(*wall->lightlist)[i].p_lightlevel) : wall->lightlevel; - FColormap thiscm; - thiscm.FadeColor = wall->Colormap.FadeColor; - thiscm.FogDensity = wall->Colormap.FogDensity; - thiscm.CopyFrom3DLight(&(*wall->lightlist)[i]); - SetColor(thisll, rel, thiscm, absalpha); - if (wall->type != RENDERWALL_M2SNF) SetFog(thisll, rel, &thiscm, wall->RenderStyle == STYLE_Add); - gl_RenderState.SetSplitPlanes((*wall->lightlist)[i].plane, lowplane); - RenderWall(wall, rflags); - } - if (low1 <= wall->zbottom[0] && low2 <= wall->zbottom[1]) break; - } - - gl_RenderState.EnableSplit(false); - } - gl_RenderState.SetObjectColor(0xffffffff); - gl_RenderState.SetObjectColor2(0); - gl_RenderState.SetTextureMode(tmode); - gl_RenderState.EnableGlow(false); -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::RenderTranslucentWall(GLWall *wall) -{ - if (wall->gltexture) - { - if (!wall->gltexture->tex->GetTranslucency()) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); - else gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - if (wall->RenderStyle == STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA,GL_ONE); - RenderTexturedWall(wall, GLWall::RWF_TEXTURED | GLWall::RWF_NOSPLIT); - if (wall->RenderStyle == STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - else - { - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - SetColor(wall->lightlevel, 0, wall->Colormap, fabsf(wall->alpha)); - SetFog(wall->lightlevel, 0, &wall->Colormap, wall->RenderStyle == STYLE_Add); - gl_RenderState.EnableTexture(false); - RenderWall(wall, GLWall::RWF_NOSPLIT); - gl_RenderState.EnableTexture(true); - } -} - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::DrawWall(GLWall *wall, int pass) -{ - if (screen->hwcaps & RFL_BUFFER_STORAGE) - { - if (level.HasDynamicLights && !isFullbrightScene() && wall->gltexture != nullptr) - { - wall->SetupLights(this, lightdata); - } - wall->MakeVertices(this, !!(wall->flags & GLWall::GLWF_TRANSLUCENT)); - } - - gl_RenderState.SetNormal(wall->glseg.Normal()); - switch (pass) - { - case GLPASS_ALL: - RenderTexturedWall(wall, GLWall::RWF_TEXTURED); - break; - - case GLPASS_TRANSLUCENT: - - switch (wall->type) - { - case RENDERWALL_MIRRORSURFACE: - RenderMirrorSurface(wall); - break; - - case RENDERWALL_FOGBOUNDARY: - RenderFogBoundary(wall); - break; - - default: - RenderTranslucentWall(wall); - break; - } - break; - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::AddWall(GLWall *wall) -{ - if (wall->flags & GLWall::GLWF_TRANSLUCENT) - { - auto newwall = drawlists[GLDL_TRANSLUCENT].NewWall(); - *newwall = *wall; - } - else - { - bool masked = GLWall::passflag[wall->type] == 1 ? false : (wall->gltexture && wall->gltexture->isMasked()); - int list; - - if ((wall->flags & GLWall::GLWF_SKYHACK && wall->type == RENDERWALL_M2S)) - { - list = GLDL_MASKEDWALLSOFS; - } - else - { - list = masked ? GLDL_MASKEDWALLS : GLDL_PLAINWALLS; - } - auto newwall = drawlists[list].NewWall(); - *newwall = *wall; - } -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::AddMirrorSurface(GLWall *w) -{ - w->type = RENDERWALL_MIRRORSURFACE; - auto newwall = drawlists[GLDL_TRANSLUCENTBORDER].NewWall(); - *newwall = *w; - - // Invalidate vertices to allow setting of texture coordinates - newwall->vertcount = 0; - - FVector3 v = newwall->glseg.Normal(); - auto tcs = newwall->tcs; - tcs[GLWall::LOLFT].u = tcs[GLWall::LORGT].u = tcs[GLWall::UPLFT].u = tcs[GLWall::UPRGT].u = v.X; - tcs[GLWall::LOLFT].v = tcs[GLWall::LORGT].v = tcs[GLWall::UPLFT].v = tcs[GLWall::UPRGT].v = v.Z; - newwall->MakeVertices(this, false); - newwall->ProcessDecals(this); -} - -//========================================================================== -// -// -// -//========================================================================== - -void FDrawInfo::AddPortal(GLWall *wall, int ptype) -{ - auto &pstate = GLRenderer->mPortalState; - IPortal * portal; - - wall->MakeVertices(this, false); - switch (ptype) - { - // portals don't go into the draw list. - // Instead they are added to the portal manager - case PORTALTYPE_HORIZON: - wall->horizon = pstate.UniqueHorizons.Get(wall->horizon); - portal = FindPortal(wall->horizon); - if (!portal) - { - portal = new GLHorizonPortal(&pstate, wall->horizon, Viewpoint); - Portals.Push(portal); - } - portal->AddLine(wall); - break; - - case PORTALTYPE_SKYBOX: - portal = FindPortal(wall->secportal); - if (!portal) - { - // either a regular skybox or an Eternity-style horizon - if (wall->secportal->mType != PORTS_SKYVIEWPOINT) portal = new GLEEHorizonPortal(&pstate, wall->secportal); - else - { - portal = new GLScenePortal(&pstate, new HWSkyboxPortal(wall->secportal)); - Portals.Push(portal); - } - } - portal->AddLine(wall); - break; - - case PORTALTYPE_SECTORSTACK: - portal = FindPortal(wall->portal); - if (!portal) - { - portal = new GLScenePortal(&pstate, new HWSectorStackPortal(wall->portal)); - Portals.Push(portal); - } - portal->AddLine(wall); - break; - - case PORTALTYPE_PLANEMIRROR: - if (pstate.PlaneMirrorMode * wall->planemirror->fC() <= 0) - { - //@sync-portal - wall->planemirror = pstate.UniquePlaneMirrors.Get(wall->planemirror); - portal = FindPortal(wall->planemirror); - if (!portal) - { - portal = new GLScenePortal(&pstate, new HWPlaneMirrorPortal(wall->planemirror)); - Portals.Push(portal); - } - portal->AddLine(wall); - } - break; - - case PORTALTYPE_MIRROR: - portal = FindPortal(wall->seg->linedef); - if (!portal) - { - portal = new GLScenePortal(&pstate, new HWMirrorPortal(wall->seg->linedef)); - Portals.Push(portal); - } - portal->AddLine(wall); - if (gl_mirror_envmap) - { - // draw a reflective layer over the mirror - AddMirrorSurface(wall); - } - break; - - case PORTALTYPE_LINETOLINE: - portal = FindPortal(wall->lineportal); - if (!portal) - { - line_t *otherside = wall->lineportal->lines[0]->mDestination; - if (otherside != nullptr && otherside->portalindex < level.linePortals.Size()) - { - ProcessActorsInPortal(otherside->getPortal()->mGroup, in_area); - } - portal = new GLScenePortal(&pstate, new HWLineToLinePortal(wall->lineportal)); - Portals.Push(portal); - } - portal->AddLine(wall); - break; - - case PORTALTYPE_SKY: - wall->sky = pstate.UniqueSkies.Get(wall->sky); - portal = FindPortal(wall->sky); - if (!portal) - { - portal = new GLSkyPortal(&pstate, wall->sky); - Portals.Push(portal); - } - portal->AddLine(wall); - break; - } - wall->vertcount = 0; -} - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::DrawDecal(GLDecal *gldecal) -{ - auto decal = gldecal->decal; - auto tex = gldecal->gltexture; - - // calculate dynamic light effect. - if (level.HasDynamicLights && !isFullbrightScene() && gl_light_sprites) - { - // Note: This should be replaced with proper shader based lighting. - double x, y; - float out[3]; - decal->GetXY(decal->Side, x, y); - GetDynSpriteLight(nullptr, x, y, gldecal->zcenter, decal->Side->lighthead, decal->Side->sector->PortalGroup, out); - gl_RenderState.SetDynLight(out[0], out[1], out[2]); - } - - // alpha color only has an effect when using an alpha texture. - if (decal->RenderStyle.Flags & STYLEF_RedIsAlpha) - { - gl_RenderState.SetObjectColor(decal->AlphaColor | 0xff000000); - } - - gl_SetRenderStyle(decal->RenderStyle, false, false); - gl_RenderState.SetMaterial(tex, CLAMP_XY, decal->Translation, 0, !!(decal->RenderStyle.Flags & STYLEF_RedIsAlpha)); - - - // If srcalpha is one it looks better with a higher alpha threshold - if (decal->RenderStyle.SrcAlpha == STYLEALPHA_One) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold); - else gl_RenderState.AlphaFunc(GL_GREATER, 0.f); - - - SetColor(gldecal->lightlevel, gldecal->rellight, gldecal->Colormap, gldecal->alpha); - // for additively drawn decals we must temporarily set the fog color to black. - PalEntry fc = gl_RenderState.GetFogColor(); - if (decal->RenderStyle.BlendOp == STYLEOP_Add && decal->RenderStyle.DestAlpha == STYLEALPHA_One) - { - gl_RenderState.SetFog(0, -1); - } - - gl_RenderState.SetNormal(gldecal->Normal); - - if (gldecal->lightlist == nullptr) - { - gl_RenderState.Apply(); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, gldecal->vertindex, 4); - } - else - { - auto &lightlist = *gldecal->lightlist; - - for (unsigned k = 0; k < lightlist.Size(); k++) - { - secplane_t &lowplane = k == lightlist.Size() - 1 ? gldecal->bottomplane : lightlist[k + 1].plane; - - DecalVertex *dv = gldecal->dv; - float low1 = lowplane.ZatPoint(dv[1].x, dv[1].y); - float low2 = lowplane.ZatPoint(dv[2].x, dv[2].y); - - if (low1 < dv[1].z || low2 < dv[2].z) - { - int thisll = lightlist[k].caster != NULL ? hw_ClampLight(*lightlist[k].p_lightlevel) : gldecal->lightlevel; - FColormap thiscm; - thiscm.FadeColor = gldecal->Colormap.FadeColor; - thiscm.CopyFrom3DLight(&lightlist[k]); - SetColor(thisll, gldecal->rellight, thiscm, gldecal->alpha); - if (level.flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) thiscm.Decolorize(); - SetFog(thisll, gldecal->rellight, &thiscm, false); - gl_RenderState.SetSplitPlanes(lightlist[k].plane, lowplane); - - gl_RenderState.Apply(); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, gldecal->vertindex, 4); - } - if (low1 <= dv[0].z && low2 <= dv[3].z) break; - } - } - - rendered_decals++; - gl_RenderState.SetTextureMode(TM_MODULATE); - gl_RenderState.SetObjectColor(0xffffffff); - gl_RenderState.SetFog(fc, -1); - gl_RenderState.SetDynLight(0, 0, 0); -} - -//========================================================================== -// -// -// -//========================================================================== -void FDrawInfo::DrawDecals() -{ - side_t *wall = nullptr; - bool splitting = false; - for (auto gldecal : decals[0]) - { - if (gldecal->decal->Side != wall) - { - wall = gldecal->decal->Side; - if (gldecal->lightlist != nullptr) - { - gl_RenderState.EnableSplit(true); - splitting = true; - } - else - { - gl_RenderState.EnableSplit(false); - splitting = false; - SetFog(gldecal->lightlevel, gldecal->rellight, &gldecal->Colormap, false); - } - } - DrawDecal(gldecal); - } - if (splitting) gl_RenderState.EnableSplit(false); -} - -//========================================================================== -// -// This list will never get long, so this code should be ok. -// -//========================================================================== -void FDrawInfo::DrawDecalsForMirror(GLWall *wall) -{ - SetFog(wall->lightlevel, wall->rellight + getExtraLight(), &wall->Colormap, false); - for (auto gldecal : decals[1]) - { - if (gldecal->decal->Side == wall->seg->sidedef) - { - DrawDecal(gldecal); - } - } -} - diff --git a/src/gl/scene/gl_weapon.cpp b/src/gl/scene/gl_weapon.cpp deleted file mode 100644 index fe5119639..000000000 --- a/src/gl/scene/gl_weapon.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// -//--------------------------------------------------------------------------- -// -// Copyright(C) 2000-2018 Christoph Oelckers -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see http://www.gnu.org/licenses/ -// -//-------------------------------------------------------------------------- -// -/* -** gl_weapon.cpp -** Weapon sprite drawing -** -*/ - -#include "gl_load/gl_system.h" -#include "r_utility.h" -#include "v_video.h" - -#include "gl_load/gl_interface.h" -#include "hwrenderer/utility/hw_cvars.h" -#include "hwrenderer/scene/hw_weapon.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_lightdata.h" -#include "gl/renderer/gl_renderstate.h" -#include "gl/data/gl_vertexbuffer.h" -#include "gl/scene/gl_drawinfo.h" -#include "gl/models/gl_models.h" -#include "gl/dynlights/gl_lightbuffer.h" - -//========================================================================== -// -// R_DrawPSprite -// -//========================================================================== - -void FDrawInfo::DrawPSprite (HUDSprite *huds) -{ - if (huds->RenderStyle.BlendOp == STYLEOP_Shadow) - { - gl_RenderState.SetColor(0.2f, 0.2f, 0.2f, 0.33f, huds->cm.Desaturation); - } - else - { - SetColor(huds->lightlevel, 0, huds->cm, huds->alpha, true); - } - gl_SetRenderStyle(huds->RenderStyle, false, false); - gl_RenderState.SetObjectColor(huds->ObjectColor); - gl_RenderState.SetDynLight(huds->dynrgb[0], huds->dynrgb[1], huds->dynrgb[2]); - gl_RenderState.EnableBrightmap(!(huds->RenderStyle.Flags & STYLEF_ColorIsFixed)); - - if (huds->mframe) - { - gl_RenderState.AlphaFunc(GL_GEQUAL, 0); - FGLModelRenderer renderer(this, huds->lightindex); - renderer.RenderHUDModel(huds->weapon, huds->mx, huds->my); - } - else - { - float thresh = (huds->tex->tex->GetTranslucency() || huds->OverrideShader != -1) ? 0.f : gl_mask_sprite_threshold; - gl_RenderState.AlphaFunc(GL_GEQUAL, thresh); - gl_RenderState.SetMaterial(huds->tex, CLAMP_XY_NOMIP, 0, huds->OverrideShader, !!(huds->RenderStyle.Flags & STYLEF_RedIsAlpha)); - gl_RenderState.Apply(); - GLRenderer->mVBO->RenderArray(GL_TRIANGLE_STRIP, huds->mx, 4); - } - - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold); - gl_RenderState.SetObjectColor(0xffffffff); - gl_RenderState.SetDynLight(0, 0, 0); - gl_RenderState.EnableBrightmap(false); -} - -//========================================================================== -// -// R_DrawPlayerSprites -// -//========================================================================== - -void FDrawInfo::DrawPlayerSprites(bool hudModelStep) -{ - int oldlightmode = level.lightmode; - if (!hudModelStep && level.lightmode == 8) level.lightmode = 2; // Software lighting cannot handle 2D content so revert to lightmode 2 for that. - for(auto &hudsprite : hudsprites) - { - if ((!!hudsprite.mframe) == hudModelStep) - DrawPSprite(&hudsprite); - } - level.lightmode = oldlightmode; -} - -void FDrawInfo::AddHUDSprite(HUDSprite *huds) -{ - hudsprites.Push(*huds); -} diff --git a/src/gl/shaders/gl_postprocessshader.cpp b/src/gl/shaders/gl_postprocessshader.cpp index d47bd095d..9c33cfdc1 100644 --- a/src/gl/shaders/gl_postprocessshader.cpp +++ b/src/gl/shaders/gl_postprocessshader.cpp @@ -34,6 +34,10 @@ CVAR(Bool, gl_custompost, true, 0) +namespace OpenGLRenderer +{ + + FCustomPostProcessShaders::FCustomPostProcessShaders() { for (unsigned int i = 0; i < PostProcessShaders.Size(); i++) @@ -242,3 +246,5 @@ void PostProcessShaderInstance::BindTextures() } } } + +} \ No newline at end of file diff --git a/src/gl/shaders/gl_postprocessshaderinstance.h b/src/gl/shaders/gl_postprocessshaderinstance.h index 75b3e3e93..02613a373 100644 --- a/src/gl/shaders/gl_postprocessshaderinstance.h +++ b/src/gl/shaders/gl_postprocessshaderinstance.h @@ -5,6 +5,10 @@ struct PostProcessShader; +namespace OpenGLRenderer +{ + + class PostProcessShaderInstance { public: @@ -40,3 +44,6 @@ private: FCustomPostProcessShaders(const FCustomPostProcessShaders &) = delete; FCustomPostProcessShaders &operator=(const FCustomPostProcessShaders &) = delete; }; + + +} \ No newline at end of file diff --git a/src/gl/shaders/gl_shader.cpp b/src/gl/shaders/gl_shader.cpp index 564365341..d4352dcb7 100644 --- a/src/gl/shaders/gl_shader.cpp +++ b/src/gl/shaders/gl_shader.cpp @@ -35,14 +35,17 @@ #include "hwrenderer/utility/hw_shaderpatcher.h" #include "hwrenderer/data/shaderuniforms.h" #include "hwrenderer/scene/hw_viewpointuniforms.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" #include "gl_load/gl_interface.h" #include "gl/system/gl_debug.h" #include "r_data/matrix.h" #include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_renderstate.h" #include "gl/shaders/gl_shader.h" -#include "gl/dynlights/gl_lightbuffer.h" + +namespace OpenGLRenderer +{ + bool FShader::Load(const char * name, const char * vert_prog_lump, const char * frag_prog_lump, const char * proc_prog_lump, const char * light_fragprog, const char * defines) { @@ -107,13 +110,6 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * // Blinn glossiness and specular level i_data += "uniform vec2 uSpecularMaterial;\n"; - // quad drawer stuff - i_data += "#ifdef USE_QUAD_DRAWER\n"; - i_data += "uniform mat4 uQuadVertices;\n"; - i_data += "uniform mat4 uQuadTexCoords;\n"; - i_data += "uniform int uQuadMode;\n"; - i_data += "#endif\n"; - // matrices i_data += "uniform mat4 ModelMatrix;\n"; i_data += "uniform mat4 NormalModelMatrix;\n"; @@ -174,11 +170,11 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * // FString vp_comb; - assert(GLRenderer->mLights != NULL); - // On the shader side there is no difference between LM_DEFERRED and LM_DIRECT, it only decides how the buffer is initialized. - unsigned int lightbuffertype = GLRenderer->mLights->GetBufferType(); - unsigned int lightbuffersize = GLRenderer->mLights->GetBlockSize(); - if (lightbuffertype == GL_UNIFORM_BUFFER) + assert(screen->mLights != NULL); + + bool lightbuffertype = screen->mLights->GetBufferType(); + unsigned int lightbuffersize = screen->mLights->GetBlockSize(); + if (!lightbuffertype) { vp_comb.Format("#version 330 core\n#define NUM_UBO_LIGHTS %d\n", lightbuffersize); } @@ -191,12 +187,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * vp_comb = "#version 430 core\n#define SHADER_STORAGE_LIGHTS\n"; } - if (gl.buffermethod == BM_DEFERRED) - { - vp_comb << "#define USE_QUAD_DRAWER\n"; - } - - if (!!(gl.flags & RFL_SHADER_STORAGE_BUFFER)) + if (gl.flags & RFL_SHADER_STORAGE_BUFFER) { vp_comb << "#define SUPPORTS_SHADOWMAPS\n"; } @@ -355,12 +346,9 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * fakevb_index = glGetUniformLocation(hShader, "fakeVB"); modelmatrix_index = glGetUniformLocation(hShader, "ModelMatrix"); texturematrix_index = glGetUniformLocation(hShader, "TextureMatrix"); - vertexmatrix_index = glGetUniformLocation(hShader, "uQuadVertices"); - texcoordmatrix_index = glGetUniformLocation(hShader, "uQuadTexCoords"); normalmodelmatrix_index = glGetUniformLocation(hShader, "NormalModelMatrix"); - quadmode_index = glGetUniformLocation(hShader, "uQuadMode"); - if (lightbuffertype == GL_UNIFORM_BUFFER) + if (!lightbuffertype) { int tempindex = glGetUniformBlockIndex(hShader, "LightBufferUBO"); if (tempindex != -1) glUniformBlockBinding(hShader, tempindex, LIGHTBUF_BINDINGPOINT); @@ -369,7 +357,6 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * if (tempindex != -1) glUniformBlockBinding(hShader, tempindex, VIEWPOINT_BINDINGPOINT); glUseProgram(hShader); - if (quadmode_index > 0) glUniform1i(quadmode_index, 0); // set up other texture units (if needed by the shader) for (int i = 2; i<16; i++) @@ -482,8 +469,6 @@ static const FDefaultShader defaultshaders[]= {nullptr,nullptr,nullptr,nullptr} }; -TArray usershaders; - struct FEffectShader { const char *ShaderName; @@ -684,3 +669,4 @@ void gl_DestroyUserShaders() // todo } +} \ No newline at end of file diff --git a/src/gl/shaders/gl_shader.h b/src/gl/shaders/gl_shader.h index 46ba3c793..732b5eaaf 100644 --- a/src/gl/shaders/gl_shader.h +++ b/src/gl/shaders/gl_shader.h @@ -28,18 +28,12 @@ extern bool gl_shaderactive; -enum -{ - VATTR_VERTEX = 0, - VATTR_TEXCOORD = 1, - VATTR_COLOR = 2, - VATTR_VERTEX2 = 3, - VATTR_NORMAL = 4 -}; - -class FShaderCollection; struct HWViewpointUniforms; +namespace OpenGLRenderer +{ + class FShaderCollection; + //========================================================================== // // @@ -234,7 +228,7 @@ public: class FShader { friend class FShaderCollection; - friend class FRenderState; + friend class FGLRenderState; unsigned int hShader; unsigned int hVertProg; @@ -269,9 +263,6 @@ class FShader public: - int vertexmatrix_index; - int texcoordmatrix_index; - int quadmode_index; int fakevb_index; private: int currentglowstate = 0; @@ -353,5 +344,6 @@ public: } }; +} #endif diff --git a/src/gl/shaders/gl_shaderprogram.cpp b/src/gl/shaders/gl_shaderprogram.cpp index 858e24375..c402bf77b 100644 --- a/src/gl/shaders/gl_shaderprogram.cpp +++ b/src/gl/shaders/gl_shaderprogram.cpp @@ -34,6 +34,9 @@ #include "hwrenderer/utility/hw_shaderpatcher.h" #include "w_wad.h" +namespace OpenGLRenderer +{ + FShaderProgram::FShaderProgram() { for (int i = 0; i < NumShaderTypes; i++) @@ -249,3 +252,5 @@ FString FShaderProgram::PatchShader(ShaderType type, const FString &code, const return patchedCode; } + +} \ No newline at end of file diff --git a/src/gl/shaders/gl_shaderprogram.h b/src/gl/shaders/gl_shaderprogram.h index c0b8a7219..f9853d5d3 100644 --- a/src/gl/shaders/gl_shaderprogram.h +++ b/src/gl/shaders/gl_shaderprogram.h @@ -5,6 +5,9 @@ #include "gl_shader.h" #include "hwrenderer/postprocessing/hw_shaderprogram.h" +namespace OpenGLRenderer +{ + class FShaderProgram : public IShaderProgram { public: @@ -21,7 +24,7 @@ public: GLuint Handle() { return mProgram; } //explicit operator bool() const { return mProgram != 0; } - std::unique_ptr Uniforms; + std::unique_ptr Uniforms; private: FShaderProgram(const FShaderProgram &) = delete; @@ -37,3 +40,5 @@ private: GLuint mShaders[NumShaderTypes]; TArray> samplerstobind; }; + +} \ No newline at end of file diff --git a/src/gl/system/gl_buffers.cpp b/src/gl/system/gl_buffers.cpp new file mode 100644 index 000000000..9ca935269 --- /dev/null +++ b/src/gl/system/gl_buffers.cpp @@ -0,0 +1,218 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** Low level vertex buffer class +** +**/ + +#include "gl_load/gl_system.h" +#include "gl_buffers.h" +#include "gl/renderer/gl_renderstate.h" + +namespace OpenGLRenderer +{ + +//========================================================================== +// +// basic buffer implementation +// +//========================================================================== + +static inline void InvalidateBufferState() +{ + gl_RenderState.ResetVertexBuffer(); // force rebinding of buffers on next Apply call. +} + +GLBuffer::GLBuffer(int usetype) + : mUseType(usetype) +{ + glGenBuffers(1, &mBufferId); +} + +GLBuffer::~GLBuffer() +{ + if (mBufferId != 0) + { + glBindBuffer(mUseType, mBufferId); + glUnmapBuffer(mUseType); + glBindBuffer(mUseType, 0); + glDeleteBuffers(1, &mBufferId); + } +} + +void GLBuffer::Bind() +{ + glBindBuffer(mUseType, mBufferId); +} + + +void GLBuffer::SetData(size_t size, void *data, bool staticdata) +{ + assert(nomap); // once it's mappable, it cannot be recreated anymore. + Bind(); + if (data != nullptr) + { + glBufferData(mUseType, size, data, staticdata? GL_STATIC_DRAW : GL_STREAM_DRAW); + } + else + { + mPersistent = screen->BuffersArePersistent() && !staticdata; + if (mPersistent) + { + glBufferStorage(mUseType, size, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + map = glMapBufferRange(mUseType, 0, size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + } + else + { + glBufferData(mUseType, size, nullptr, staticdata ? GL_STATIC_DRAW : GL_STREAM_DRAW); + map = nullptr; + } + if (!staticdata) nomap = false; + } + buffersize = size; + InvalidateBufferState(); +} + +void GLBuffer::Map() +{ + assert(nomap == false); // do not allow mapping of static buffers. Vulkan cannot do that so it should be blocked in OpenGL, too. + if (!mPersistent && !nomap) + { + Bind(); + map = (FFlatVertex*)glMapBufferRange(mUseType, 0, buffersize, GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); + InvalidateBufferState(); + } +} + +void GLBuffer::Unmap() +{ + assert(nomap == false); + if (!mPersistent && map != nullptr) + { + Bind(); + glUnmapBuffer(mUseType); + InvalidateBufferState(); + map = nullptr; + } +} + +void *GLBuffer::Lock(unsigned int size) +{ + // This initializes this buffer as a static object with no data. + SetData(size, nullptr, true); + return glMapBufferRange(mUseType, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT); +} + +void GLBuffer::Unlock() +{ + Bind(); + glUnmapBuffer(mUseType); + InvalidateBufferState(); +} + +void GLBuffer::Resize(size_t newsize) +{ + assert(!nomap); // only mappable buffers can be resized. + if (newsize > buffersize && !nomap) + { + // reallocate the buffer with twice the size + unsigned int oldbuffer = mBufferId; + + // first unmap the old buffer + Bind(); + glUnmapBuffer(mUseType); + + glGenBuffers(1, &mBufferId); + SetData(newsize, nullptr, false); + glBindBuffer(GL_COPY_READ_BUFFER, oldbuffer); + + // copy contents and delete the old buffer. + glCopyBufferSubData(GL_COPY_READ_BUFFER, mUseType, 0, 0, buffersize); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + glDeleteBuffers(1, &oldbuffer); + buffersize = newsize; + InvalidateBufferState(); + } +} + + +//=========================================================================== +// +// Vertex buffer implementation +// +//=========================================================================== + +void GLVertexBuffer::SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) +{ + static int VFmtToGLFmt[] = { GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE, GL_INT_2_10_10_10_REV }; + static uint8_t VFmtToSize[] = {4, 3, 2, 1, 4, 4}; + + mStride = stride; + mNumBindingPoints = numBindingPoints; + + for(int i = 0; i < numAttributes; i++) + { + if (attrs[i].location >= 0 && attrs[i].location < VATTR_MAX) + { + auto & attrinf = mAttributeInfo[attrs[i].location]; + attrinf.format = VFmtToGLFmt[attrs[i].format]; + attrinf.size = VFmtToSize[attrs[i].format]; + attrinf.offset = attrs[i].offset; + attrinf.bindingpoint = attrs[i].binding; + } + } +} + +void GLVertexBuffer::Bind(int *offsets) +{ + int i = 0; + + // This is what gets called from RenderState.Apply. It shouldn't be called anywhere else if the render state is in use + GLBuffer::Bind(); + for(auto &attrinf : mAttributeInfo) + { + if (attrinf.size == 0) + { + glDisableVertexAttribArray(i); + } + else + { + glEnableVertexAttribArray(i); + size_t ofs = offsets == nullptr ? attrinf.offset : attrinf.offset + mStride * offsets[attrinf.bindingpoint]; + glVertexAttribPointer(i, attrinf.size, attrinf.format, attrinf.format != GL_FLOAT, (GLsizei)mStride, (void*)(intptr_t)ofs); + } + i++; + } +} + +void GLDataBuffer::BindRange(size_t start, size_t length) +{ + if (mUseType == GL_UNIFORM_BUFFER) // SSBO's cannot be rebound. + glBindBufferRange(mUseType, mBindingPoint, mBufferId, start, length); +} + +void GLDataBuffer::BindBase() +{ + glBindBufferBase(mUseType, mBindingPoint, mBufferId); +} + +} \ No newline at end of file diff --git a/src/gl/system/gl_buffers.h b/src/gl/system/gl_buffers.h new file mode 100644 index 000000000..f2d636cf1 --- /dev/null +++ b/src/gl/system/gl_buffers.h @@ -0,0 +1,72 @@ +#pragma once + +#include "hwrenderer/data/buffers.h" + +#ifdef _MSC_VER +// silence bogus warning C4250: 'GLVertexBuffer': inherits 'GLBuffer::GLBuffer::SetData' via dominance +// According to internet infos, the warning is erroneously emitted in this case. +#pragma warning(disable:4250) +#endif + +namespace OpenGLRenderer +{ + +class GLBuffer : virtual public IBuffer +{ +protected: + const int mUseType; + unsigned int mBufferId; + int mAllocationSize = 0; + bool mPersistent = false; + bool nomap = true; + + GLBuffer(int usetype); + ~GLBuffer(); + void SetData(size_t size, void *data, bool staticdata) override; + void Map() override; + void Unmap() override; + void Resize(size_t newsize) override; + void *Lock(unsigned int size) override; + void Unlock() override; +public: + void Bind(); +}; + + +class GLVertexBuffer : public IVertexBuffer, public GLBuffer +{ + // If this could use the modern (since GL 4.3) binding system, things would be simpler... :( + struct GLVertexBufferAttribute + { + int bindingpoint; + int format; + int size; + int offset; + }; + + int mNumBindingPoints; + GLVertexBufferAttribute mAttributeInfo[VATTR_MAX] = {}; // Thanks to OpenGL's state system this needs to contain info about every attribute that may ever be in use throughout the entire renderer. + size_t mStride = 0; + +public: + GLVertexBuffer() : GLBuffer(GL_ARRAY_BUFFER) {} + void SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) override; + void Bind(int *offsets); +}; + +class GLIndexBuffer : public IIndexBuffer, public GLBuffer +{ +public: + GLIndexBuffer() : GLBuffer(GL_ELEMENT_ARRAY_BUFFER) {} +}; + +class GLDataBuffer : public IDataBuffer, public GLBuffer +{ + int mBindingPoint; +public: + GLDataBuffer(int bindingpoint, bool is_ssbo) : GLBuffer(is_ssbo? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER), mBindingPoint(bindingpoint) {} + void BindRange(size_t start, size_t length) override; + void BindBase() override; +}; + +} \ No newline at end of file diff --git a/src/gl/system/gl_debug.cpp b/src/gl/system/gl_debug.cpp index 920550c9b..56dcf3727 100644 --- a/src/gl/system/gl_debug.cpp +++ b/src/gl/system/gl_debug.cpp @@ -35,7 +35,7 @@ CUSTOM_CVAR(Int, gl_debug_level, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { - if (!FGLDebug::HasDebugApi()) + if (!OpenGLRenderer::FGLDebug::HasDebugApi()) { Printf("No OpenGL debug support detected.\n"); } @@ -43,6 +43,9 @@ CUSTOM_CVAR(Int, gl_debug_level, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOIN CVAR(Bool, gl_debug_breakpoint, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); +namespace OpenGLRenderer +{ + namespace { bool gpuStatActive = false; @@ -368,3 +371,5 @@ FString FGLDebug::SeverityToString(GLenum severity) } return s; } + +} \ No newline at end of file diff --git a/src/gl/system/gl_debug.h b/src/gl/system/gl_debug.h index 191e027f5..a234a0879 100644 --- a/src/gl/system/gl_debug.h +++ b/src/gl/system/gl_debug.h @@ -7,6 +7,9 @@ #include "r_defs.h" #include "v_video.h" +namespace OpenGLRenderer +{ + class FGLDebug { public: @@ -38,4 +41,5 @@ private: bool mBreakpointMode = false; }; +} #endif diff --git a/src/gl/system/gl_framebuffer.cpp b/src/gl/system/gl_framebuffer.cpp index 729ecad1f..d3d242cd4 100644 --- a/src/gl/system/gl_framebuffer.cpp +++ b/src/gl/system/gl_framebuffer.cpp @@ -38,19 +38,26 @@ #include "gl/textures/gl_samplers.h" #include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/utility/hw_vrmodes.h" -#include "gl/data/gl_vertexbuffer.h" -#include "gl/data/gl_uniformbuffer.h" -#include "gl/models/gl_models.h" +#include "hwrenderer/models/hw_models.h" +#include "hwrenderer/scene/hw_skydome.h" +#include "hwrenderer/data/hw_viewpointbuffer.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" #include "gl/shaders/gl_shaderprogram.h" #include "gl_debug.h" #include "r_videoscale.h" +#include "gl_buffers.h" + +#include "hwrenderer/data/flatvertices.h" EXTERN_CVAR (Bool, vid_vsync) - -FGLRenderer *GLRenderer; +EXTERN_CVAR(Bool, r_drawvoxels) +EXTERN_CVAR(Int, gl_tonemap) +EXTERN_CVAR(Bool, gl_texture_usehires) void gl_LoadExtensions(); void gl_PrintStartupLog(); +void Draw2D(F2DDrawer *drawer, FRenderState &state); + CUSTOM_CVAR(Int, vid_hwgamma, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { @@ -58,6 +65,10 @@ CUSTOM_CVAR(Int, vid_hwgamma, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITC if (screen != nullptr) screen->SetGamma(); } +namespace OpenGLRenderer +{ + FGLRenderer *GLRenderer; + //========================================================================== // // @@ -75,23 +86,22 @@ OpenGLFrameBuffer::OpenGLFrameBuffer(void *hMonitor, bool fullscreen) : FHardwareTexture::InitGlobalState(); gl_RenderState.Reset(); - GLRenderer = new FGLRenderer(this); + GLRenderer = nullptr; InitPalette(); - - InitializeState(); - mDebug = std::make_shared(); - mDebug->Update(); - SetGamma(); - - // Move some state to the framebuffer object for easier access. - hwcaps = gl.flags; - glslversion = gl.glslversion; } OpenGLFrameBuffer::~OpenGLFrameBuffer() { - delete GLRenderer; - GLRenderer = NULL; + if (mVertexData != nullptr) delete mVertexData; + if (mSkyData != nullptr) delete mSkyData; + if (mViewpoints != nullptr) delete mViewpoints; + if (mLights != nullptr) delete mLights; + + if (GLRenderer) + { + delete GLRenderer; + GLRenderer = nullptr; + } } //========================================================================== @@ -114,14 +124,19 @@ void OpenGLFrameBuffer::InitializeState() gl_LoadExtensions(); + // Move some state to the framebuffer object for easier access. + hwcaps = gl.flags; + glslversion = gl.glslversion; + uniformblockalignment = gl.uniformblockalignment; + maxuniformblock = gl.maxuniformblock; + gl_vendorstring = gl.vendorstring; + if (first) { first=false; gl_PrintStartupLog(); } - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClearDepth(1.0f); glDepthFunc(GL_LESS); glEnable(GL_DITHER); @@ -134,10 +149,22 @@ void OpenGLFrameBuffer::InitializeState() glDisable(GL_LINE_SMOOTH); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GLRenderer->Initialize(GetWidth(), GetHeight()); SetViewportRects(nullptr); + + mVertexData = new FFlatVertexBuffer(GetWidth(), GetHeight()); + mSkyData = new FSkyVertexBuffer; + mViewpoints = new GLViewpointBuffer; + mLights = new FLightBuffer(); + + GLRenderer = new FGLRenderer(this); + GLRenderer->Initialize(GetWidth(), GetHeight()); + + mDebug = std::make_shared(); + mDebug->Update(); } //========================================================================== @@ -156,20 +183,7 @@ void OpenGLFrameBuffer::Update() Flush3D.Unclock(); Swap(); - CheckBench(); - - int initialWidth = GetClientWidth(); - int initialHeight = GetClientHeight(); - int clientWidth = ViewportScaledWidth(initialWidth, initialHeight); - int clientHeight = ViewportScaledHeight(initialWidth, initialHeight); - if (clientWidth < 320) clientWidth = 320; - if (clientHeight < 200) clientHeight = 200; - if (clientWidth > 0 && clientHeight > 0 && (GetWidth() != clientWidth || GetHeight() != clientHeight)) - { - SetVirtualSize(clientWidth, clientHeight); - V_OutputResized(clientWidth, clientHeight); - GLRenderer->mVBO->OutputResized(clientWidth, clientHeight); - } + Super::Update(); } //=========================================================================== @@ -226,9 +240,6 @@ sector_t *OpenGLFrameBuffer::RenderView(player_t *player) // //=========================================================================== -EXTERN_CVAR(Bool, r_drawvoxels) -EXTERN_CVAR(Int, gl_tonemap) - uint32_t OpenGLFrameBuffer::GetCaps() { if (!V_IsHardwareRenderer()) @@ -341,17 +352,30 @@ IHardwareTexture *OpenGLFrameBuffer::CreateHardwareTexture(FTexture *tex) void OpenGLFrameBuffer::PrecacheMaterial(FMaterial *mat, int translation) { - gl_RenderState.SetMaterial(mat, CLAMP_NONE, translation, false, false); + auto tex = mat->tex; + if (tex->UseType == ETextureType::SWCanvas) return; + + // Textures that are already scaled in the texture lump will not get replaced by hires textures. + int flags = mat->isExpanded() ? CTF_Expand : (gl_texture_usehires && tex->Scale.X == 1 && tex->Scale.Y == 1) ? CTF_CheckHires : 0; + int numLayers = mat->GetLayers(); + auto base = static_cast(mat->GetLayer(0)); + + if (base->BindOrCreate(tex, 0, CLAMP_NONE, translation, flags)) + { + for (int i = 1; i < numLayers; i++) + { + FTexture *layer; + auto systex = static_cast(mat->GetLayer(i, &layer)); + systex->BindOrCreate(layer, i, CLAMP_NONE, 0, mat->isExpanded() ? CTF_Expand : 0); + } + } + // unbind everything. + FHardwareTexture::UnbindAll(); } FModelRenderer *OpenGLFrameBuffer::CreateModelRenderer(int mli) { - return new FGLModelRenderer(nullptr, mli); -} - -IUniformBuffer *OpenGLFrameBuffer::CreateUniformBuffer(size_t size, bool staticuse) -{ - return new GLUniformBuffer(size, staticuse); + return new FGLModelRenderer(nullptr, gl_RenderState, mli); } IShaderProgram *OpenGLFrameBuffer::CreateShaderProgram() @@ -359,6 +383,20 @@ IShaderProgram *OpenGLFrameBuffer::CreateShaderProgram() return new FShaderProgram; } +IVertexBuffer *OpenGLFrameBuffer::CreateVertexBuffer() +{ + return new GLVertexBuffer; +} + +IIndexBuffer *OpenGLFrameBuffer::CreateIndexBuffer() +{ + return new GLIndexBuffer; +} + +IDataBuffer *OpenGLFrameBuffer::CreateDataBuffer(int bindingpoint, bool ssbo) +{ + return new GLDataBuffer(bindingpoint, ssbo); +} void OpenGLFrameBuffer::TextureFilterChanged() { @@ -380,15 +418,6 @@ void OpenGLFrameBuffer::SetViewportRects(IntRect *bounds) } } - -void OpenGLFrameBuffer::InitForLevel() -{ - if (GLRenderer != NULL) - { - GLRenderer->SetupLevel(); - } -} - void OpenGLFrameBuffer::UpdatePalette() { if (GLRenderer) @@ -402,15 +431,6 @@ void OpenGLFrameBuffer::UpdatePalette() // //=========================================================================== -void OpenGLFrameBuffer::SetClearColor(int color) -{ - PalEntry pe = GPalette.BaseColors[color]; - GLRenderer->mSceneClearColor[0] = pe.r / 255.f; - GLRenderer->mSceneClearColor[1] = pe.g / 255.f; - GLRenderer->mSceneClearColor[2] = pe.b / 255.f; -} - - void OpenGLFrameBuffer::BeginFrame() { SetViewportRects(nullptr); @@ -476,7 +496,15 @@ void OpenGLFrameBuffer::GetScreenshotBuffer(const uint8_t *&buffer, int &pitch, void OpenGLFrameBuffer::Draw2D() { - if (GLRenderer != nullptr) GLRenderer->Draw2D(&m2DDrawer); + if (GLRenderer != nullptr) + { + FGLDebug::PushGroup("Draw2D"); + if (VRMode::GetVRMode(true)->mEyeCount == 1) + GLRenderer->mBuffers->BindCurrentFB(); + + ::Draw2D(&m2DDrawer, gl_RenderState); + FGLDebug::PopGroup(); + } } void OpenGLFrameBuffer::PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D) @@ -484,28 +512,6 @@ void OpenGLFrameBuffer::PostProcessScene(int fixedcm, const std::functionPostProcessScene(fixedcm, afterBloomDrawEndScene2D); } -//========================================================================== -// -// This is just a wrapper around the hardware texture being extracted below so that it can be passed to the 2D code. -// -//========================================================================== - -class FGLWipeTexture : public FTexture -{ -public: - - FGLWipeTexture(int w, int h) - { - Width = w; - Height = h; - WidthBits = 4; - UseType = ETextureType::SWCanvas; - bNoCompress = true; - SystemTexture[0] = screen->CreateHardwareTexture(this); - } - -}; - //========================================================================== // // OpenGLFrameBuffer :: WipeStartScreen @@ -520,7 +526,7 @@ FTexture *OpenGLFrameBuffer::WipeStartScreen() { const auto &viewport = screen->mScreenViewport; - FGLWipeTexture *tex = new FGLWipeTexture(viewport.width, viewport.height); + auto tex = new FWrapperTexture(viewport.width, viewport.height, 1); tex->SystemTexture[0]->CreateTexture(nullptr, viewport.width, viewport.height, 0, false, 0, "WipeStartScreen"); glFinish(); static_cast(tex->SystemTexture[0])->Bind(0, false, false); @@ -542,7 +548,7 @@ FTexture *OpenGLFrameBuffer::WipeEndScreen() { GLRenderer->Flush(); const auto &viewport = screen->mScreenViewport; - FGLWipeTexture *tex = new FGLWipeTexture(viewport.width, viewport.height); + auto tex = new FWrapperTexture(viewport.width, viewport.height, 1); tex->SystemTexture[0]->CreateTexture(NULL, viewport.width, viewport.height, 0, false, 0, "WipeEndScreen"); glFinish(); static_cast(tex->SystemTexture[0])->Bind(0, false, false); @@ -551,3 +557,4 @@ FTexture *OpenGLFrameBuffer::WipeEndScreen() return tex; } +} \ No newline at end of file diff --git a/src/gl/system/gl_framebuffer.h b/src/gl/system/gl_framebuffer.h index 44a380b3a..c2a26650f 100644 --- a/src/gl/system/gl_framebuffer.h +++ b/src/gl/system/gl_framebuffer.h @@ -5,8 +5,10 @@ #include +namespace OpenGLRenderer +{ + class FHardwareTexture; -class FSimpleVertexBuffer; class FGLDebug; class OpenGLFrameBuffer : public SystemGLFrameBuffer @@ -19,7 +21,7 @@ public: OpenGLFrameBuffer(void *hMonitor, bool fullscreen) ; ~OpenGLFrameBuffer(); - void InitializeState(); + void InitializeState() override; void Update() override; // Color correction @@ -27,8 +29,6 @@ public: void CleanForRestart() override; void UpdatePalette() override; - void InitForLevel() override; - void SetClearColor(int color) override; uint32_t GetCaps() override; void RenderTextureView(FCanvasTexture *tex, AActor *Viewpoint, double FOV) override; void WriteSavePic(player_t *player, FileWriter *file, int width, int height) override; @@ -41,8 +41,10 @@ public: void BeginFrame() override; void SetViewportRects(IntRect *bounds) override; void BlurScene(float amount) override; - IUniformBuffer *CreateUniformBuffer(size_t size, bool staticuse = false) override; - IShaderProgram *CreateShaderProgram() override; + IShaderProgram *CreateShaderProgram() override; + IVertexBuffer *CreateVertexBuffer() override; + IIndexBuffer *CreateIndexBuffer() override; + IDataBuffer *CreateDataBuffer(int bindingpoint, bool ssbo) override; // Retrieves a buffer containing image data for a screenshot. // Hint: Pitch can be negative for upside-down images, in which case buffer @@ -66,5 +68,6 @@ private: int camtexcount = 0; }; +} #endif //__GL_FRAMEBUFFER diff --git a/src/gl/textures/gl_hwtexture.cpp b/src/gl/textures/gl_hwtexture.cpp index 3651c016c..568c05be1 100644 --- a/src/gl/textures/gl_hwtexture.cpp +++ b/src/gl/textures/gl_hwtexture.cpp @@ -39,6 +39,9 @@ #include "gl/renderer/gl_renderstate.h" #include "gl/textures/gl_samplers.h" +namespace OpenGLRenderer +{ + TexFilter_s TexFilter[]={ {GL_NEAREST, GL_NEAREST, false}, @@ -71,105 +74,6 @@ int TexFormat[]={ //=========================================================================== unsigned int FHardwareTexture::lastbound[FHardwareTexture::MAX_TEXTURES]; -//=========================================================================== -// -// Quick'n dirty image rescaling. -// -// This will only be used when the source texture is larger than -// what the hardware can manage (extremely rare in Doom) -// -// Code taken from wxWidgets -// -//=========================================================================== - -struct BoxPrecalc -{ - int boxStart; - int boxEnd; -}; - -static void ResampleBoxPrecalc(TArray& boxes, int oldDim) -{ - int newDim = boxes.Size(); - const double scale_factor_1 = double(oldDim) / newDim; - const int scale_factor_2 = (int)(scale_factor_1 / 2); - - for (int dst = 0; dst < newDim; ++dst) - { - // Source pixel in the Y direction - const int src_p = int(dst * scale_factor_1); - - BoxPrecalc& precalc = boxes[dst]; - precalc.boxStart = clamp(int(src_p - scale_factor_1 / 2.0 + 1), 0, oldDim - 1); - precalc.boxEnd = clamp(MAX(precalc.boxStart + 1, int(src_p + scale_factor_2)), 0, oldDim - 1); - } -} - -void FHardwareTexture::Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data) -{ - - // This function implements a simple pre-blur/box averaging method for - // downsampling that gives reasonably smooth results To scale the image - // down we will need to gather a grid of pixels of the size of the scale - // factor in each direction and then do an averaging of the pixels. - - TArray vPrecalcs(height, true); - TArray hPrecalcs(width, true); - - ResampleBoxPrecalc(vPrecalcs, sheight); - ResampleBoxPrecalc(hPrecalcs, swidth); - - int averaged_pixels, averaged_alpha, src_pixel_index; - double sum_r, sum_g, sum_b, sum_a; - - for (int y = 0; y < height; y++) // Destination image - Y direction - { - // Source pixel in the Y direction - const BoxPrecalc& vPrecalc = vPrecalcs[y]; - - for (int x = 0; x < width; x++) // Destination image - X direction - { - // Source pixel in the X direction - const BoxPrecalc& hPrecalc = hPrecalcs[x]; - - // Box of pixels to average - averaged_pixels = 0; - averaged_alpha = 0; - sum_r = sum_g = sum_b = sum_a = 0.0; - - for (int j = vPrecalc.boxStart; j <= vPrecalc.boxEnd; ++j) - { - for (int i = hPrecalc.boxStart; i <= hPrecalc.boxEnd; ++i) - { - // Calculate the actual index in our source pixels - src_pixel_index = j * swidth + i; - - int a = src_data[src_pixel_index * 4 + 3]; - if (a > 0) // do not use color from fully transparent pixels - { - sum_r += src_data[src_pixel_index * 4 + 0]; - sum_g += src_data[src_pixel_index * 4 + 1]; - sum_b += src_data[src_pixel_index * 4 + 2]; - sum_a += a; - averaged_pixels++; - } - averaged_alpha++; - - } - } - - // Calculate the average from the sum and number of averaged pixels - dst_data[0] = (unsigned char)xs_CRoundToInt(sum_r / averaged_pixels); - dst_data[1] = (unsigned char)xs_CRoundToInt(sum_g / averaged_pixels); - dst_data[2] = (unsigned char)xs_CRoundToInt(sum_b / averaged_pixels); - dst_data[3] = (unsigned char)xs_CRoundToInt(sum_a / averaged_alpha); - dst_data += 4; - } - } -} - - - //=========================================================================== // // Loads the texture image into the hardware @@ -563,3 +467,4 @@ bool FHardwareTexture::BindOrCreate(FTexture *tex, int texunit, int clampmode, i return true; } +} \ No newline at end of file diff --git a/src/gl/textures/gl_hwtexture.h b/src/gl/textures/gl_hwtexture.h index ae9a22783..6c693a998 100644 --- a/src/gl/textures/gl_hwtexture.h +++ b/src/gl/textures/gl_hwtexture.h @@ -16,6 +16,9 @@ class FCanvasTexture; class AActor; +namespace OpenGLRenderer +{ + // For error catching while changing parameters. enum EInvalid @@ -66,7 +69,6 @@ private: TranslatedTexture * GetTexID(int translation); int GetDepthBuffer(int w, int h); - void Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data); public: FHardwareTexture(bool nocompress); @@ -91,4 +93,5 @@ public: void CleanUnused(SpriteHits &usedtranslations); }; +} #endif diff --git a/src/gl/textures/gl_samplers.cpp b/src/gl/textures/gl_samplers.cpp index ed001ada9..e5fd90281 100644 --- a/src/gl/textures/gl_samplers.cpp +++ b/src/gl/textures/gl_samplers.cpp @@ -30,6 +30,9 @@ #include "gl_samplers.h" #include "hwrenderer/textures/hw_material.h" +namespace OpenGLRenderer +{ + extern TexFilter_s TexFilter[]; @@ -98,3 +101,4 @@ void FSamplerManager::SetTextureFilterMode() } +} \ No newline at end of file diff --git a/src/gl/textures/gl_samplers.h b/src/gl/textures/gl_samplers.h index 0783e5aef..93c8c7419 100644 --- a/src/gl/textures/gl_samplers.h +++ b/src/gl/textures/gl_samplers.h @@ -3,6 +3,9 @@ #include "gl_hwtexture.h" +namespace OpenGLRenderer +{ + class FSamplerManager { // We need 6 different samplers: 4 for the different clamping modes, @@ -22,6 +25,6 @@ public: }; - +} #endif diff --git a/src/gl_load/gl_interface.cpp b/src/gl_load/gl_interface.cpp index c9423b15f..d690a1a15 100644 --- a/src/gl_load/gl_interface.cpp +++ b/src/gl_load/gl_interface.cpp @@ -154,7 +154,7 @@ void gl_LoadExtensions() // Don't even start if it's lower than 2.0 or no framebuffers are available (The framebuffer extension is needed for glGenerateMipmapsEXT!) if (gl_version < 3.3f) { - I_FatalError("Unsupported OpenGL version.\nAt least OpenGL 3.3 with framebuffer support is required to run " GAMENAME ".\nFor older versions of OpenGL please download the vintage build of " GAMENAME ".\n"); + I_FatalError("Unsupported OpenGL version.\nAt least OpenGL 3.3 is required to run " GAMENAME ".\nFor older versions of OpenGL please download the vintage build of " GAMENAME ".\n"); } @@ -168,8 +168,6 @@ void gl_LoadExtensions() if (CheckExtension("GL_ARB_texture_compression")) gl.flags |= RFL_TEXTURE_COMPRESSION; if (CheckExtension("GL_EXT_texture_compression_s3tc")) gl.flags |= RFL_TEXTURE_COMPRESSION_S3TC; - gl.lightmethod = LM_DEFERRED; - gl.buffermethod = BM_DEFERRED; if (gl_version < 4.f) { #ifdef _WIN32 @@ -192,16 +190,12 @@ void gl_LoadExtensions() gl.flags |= RFL_SHADER_STORAGE_BUFFER; } gl.flags |= RFL_BUFFER_STORAGE; - gl.lightmethod = LM_DIRECT; - gl.buffermethod = BM_PERSISTENT; } } else { // Assume that everything works without problems on GL 4.5 drivers where these things are core features. gl.flags |= RFL_SHADER_STORAGE_BUFFER | RFL_BUFFER_STORAGE; - gl.lightmethod = LM_DIRECT; - gl.buffermethod = BM_PERSISTENT; } // Mesa implements shader storage only for fragment shaders. @@ -215,18 +209,6 @@ void gl_LoadExtensions() if (gl_version >= 4.3f || CheckExtension("GL_ARB_invalidate_subdata")) gl.flags |= RFL_INVALIDATE_BUFFER; if (gl_version >= 4.3f || CheckExtension("GL_KHR_debug")) gl.flags |= RFL_DEBUG; - const char *lm = Args->CheckValue("-lightmethod"); - if (lm != NULL) - { - if (!stricmp(lm, "deferred") && gl.lightmethod == LM_DIRECT) gl.lightmethod = LM_DEFERRED; - } - - lm = Args->CheckValue("-buffermethod"); - if (lm != NULL) - { - if (!stricmp(lm, "deferred") && gl.buffermethod == BM_PERSISTENT) gl.buffermethod = BM_DEFERRED; - } - glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &v); gl.maxuniforms = v; glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &v); diff --git a/src/gl_load/gl_interface.h b/src/gl_load/gl_interface.h index f12e21fe4..f1f580127 100644 --- a/src/gl_load/gl_interface.h +++ b/src/gl_load/gl_interface.h @@ -3,40 +3,12 @@ #include "basictypes.h" -enum TexMode -{ - TM_MODULATE = 0, // (r, g, b, a) - TM_MASK, // (1, 1, 1, a) - TM_OPAQUE, // (r, g, b, 1) - TM_INVERSE, // (1-r, 1-g, 1-b, a) - TM_REDTOALPHA, // (1, 1, 1, r) - TM_CLAMPY, // (r, g, b, (t >= 0.0 && t <= 1.0)? a:0) - TM_INVERTOPAQUE, // (1-r, 1-g, 1-b, 1) - TM_FOGLAYER, // (renders a fog layer in the shape of the active texture) - TM_FIXEDCOLORMAP = TM_FOGLAYER, // repurposes the objectcolor uniforms to render a fixed colormap range. (Same constant because they cannot be used in the same context. -}; - -enum ELightMethod -{ - LM_DEFERRED = 1, // calculate lights up front in a separate pass - LM_DIRECT = 2, // calculate lights on the fly along with the render data -}; - -enum EBufferMethod -{ - BM_DEFERRED = 1, // use a temporarily mapped buffer, for GL 3.x core profile - BM_PERSISTENT = 2 // use a persistently mapped buffer -}; - - struct RenderContext { unsigned int flags; unsigned int maxuniforms; unsigned int maxuniformblock; unsigned int uniformblockalignment; - int lightmethod; - int buffermethod; float glslversion; int max_texturesize; char * vendorstring; diff --git a/src/hwrenderer/data/buffers.h b/src/hwrenderer/data/buffers.h new file mode 100644 index 000000000..9707a6eea --- /dev/null +++ b/src/hwrenderer/data/buffers.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +// The low level code needs to know which attributes exist. +// OpenGL needs to change the state of all of them per buffer binding. +// VAOs are mostly useless for this because they lump buffer and binding state together which the model code does not want. +enum +{ + VATTR_VERTEX, + VATTR_TEXCOORD, + VATTR_COLOR, + VATTR_VERTEX2, + VATTR_NORMAL, + VATTR_NORMAL2, + + VATTR_MAX +}; + +enum EVertexAttributeFormat +{ + VFmt_Float4, + VFmt_Float3, + VFmt_Float2, + VFmt_Float, + VFmt_Byte4, + VFmt_Packed_A2R10G10B10, +}; + +struct FVertexBufferAttribute +{ + int binding; + int location; + int format; + int offset; +}; + +class IBuffer +{ +protected: + size_t buffersize = 0; + void *map = nullptr; +public: + IBuffer() = default; + IBuffer(const IBuffer &) = delete; + IBuffer &operator=(const IBuffer &) = delete; + virtual ~IBuffer() = default; + + virtual void SetData(size_t size, void *data, bool staticdata = true) = 0; + virtual void *Lock(unsigned int size) = 0; + virtual void Unlock() = 0; + virtual void Resize(size_t newsize) = 0; + virtual void Map() {} // Only needed by old OpenGL but this needs to be in the interface. + virtual void Unmap() {} + void *Memory() { assert(map); return map; } + size_t Size() { return buffersize; } +}; + +class IVertexBuffer : virtual public IBuffer +{ +public: + virtual void SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) = 0; +}; + +// This merely exists to have a dedicated type for index buffers to inherit from. +class IIndexBuffer : virtual public IBuffer +{ + // Element size is fixed to 4, thanks to OpenGL requiring this info to be coded into the glDrawElements call. + // This mostly prohibits a more flexible buffer setup but GZDoom doesn't use any other format anyway. + // Ob Vulkam, element size is a buffer property and of no concern to the drawing functions (as it should be.) +}; + +class IDataBuffer : virtual public IBuffer +{ + // Can be either uniform or shader storage buffer, depending on its needs. +public: + virtual void BindRange(size_t start, size_t length) = 0; + virtual void BindBase() = 0; + +}; diff --git a/src/hwrenderer/data/flatvertices.cpp b/src/hwrenderer/data/flatvertices.cpp index 1decb2c04..20e139c58 100644 --- a/src/hwrenderer/data/flatvertices.cpp +++ b/src/hwrenderer/data/flatvertices.cpp @@ -31,6 +31,9 @@ #include "c_cvars.h" #include "g_levellocals.h" #include "flatvertices.h" +#include "cmdlib.h" +#include "hwrenderer/data/buffers.h" +#include "hwrenderer/scene/hw_renderstate.h" //========================================================================== @@ -39,7 +42,7 @@ // //========================================================================== -FFlatVertexGenerator::FFlatVertexGenerator(int width, int height) +FFlatVertexBuffer::FFlatVertexBuffer(int width, int height) { vbo_shadowdata.Resize(NUM_RESERVED); @@ -71,14 +74,51 @@ FFlatVertexGenerator::FFlatVertexGenerator(int width, int height) vbo_shadowdata[17].Set(-32767.0f, -32767.0f, 32767.0f, 0, 0); vbo_shadowdata[18].Set(32767.0f, -32767.0f, 32767.0f, 0, 0); vbo_shadowdata[19].Set(32767.0f, -32767.0f, -32767.0f, 0, 0); + + mVertexBuffer = screen->CreateVertexBuffer(); + mIndexBuffer = screen->CreateIndexBuffer(); + + unsigned int bytesize = BUFFER_SIZE * sizeof(FFlatVertex); + mVertexBuffer->SetData(bytesize, nullptr, false); + + static const FVertexBufferAttribute format[] = { + { 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(FFlatVertex, x) }, + { 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FFlatVertex, u) } + }; + mVertexBuffer->SetFormat(1, 2, sizeof(FFlatVertex), format); + + mIndex = mCurIndex = 0; + mNumReserved = NUM_RESERVED; + Copy(0, NUM_RESERVED); } -void FFlatVertexGenerator::OutputResized(int width, int height) +//========================================================================== +// +// +// +//========================================================================== + +FFlatVertexBuffer::~FFlatVertexBuffer() +{ + delete mIndexBuffer; + delete mVertexBuffer; + mIndexBuffer = nullptr; + mVertexBuffer = nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FFlatVertexBuffer::OutputResized(int width, int height) { vbo_shadowdata[4].Set(0, 0, 0, 0, 0); vbo_shadowdata[5].Set(0, (float)height, 0, 0, 1); vbo_shadowdata[6].Set((float)width, 0, 0, 1, 0); vbo_shadowdata[7].Set((float)width, (float)height, 0, 1, 1); + Copy(4, 4); } //========================================================================== @@ -118,7 +158,7 @@ static F3DFloor *Find3DFloor(sector_t *target, sector_t *model) // //========================================================================== -int FFlatVertexGenerator::CreateIndexedSubsectorVertices(subsector_t *sub, const secplane_t &plane, int floor, int vi, FFlatVertexGenerator::FIndexGenerationInfo &gen) +int FFlatVertexBuffer::CreateIndexedSubsectorVertices(subsector_t *sub, const secplane_t &plane, int floor, int vi, FFlatVertexBuffer::FIndexGenerationInfo &gen) { if (sub->numlines < 3) return -1; @@ -144,7 +184,7 @@ int FFlatVertexGenerator::CreateIndexedSubsectorVertices(subsector_t *sub, const // //========================================================================== -int FFlatVertexGenerator::CreateIndexedSectorVertices(sector_t *sec, const secplane_t &plane, int floor, FFlatVertexGenerator::FIndexGenerationInfo &gen) +int FFlatVertexBuffer::CreateIndexedSectorVertices(sector_t *sec, const secplane_t &plane, int floor, FFlatVertexBuffer::FIndexGenerationInfo &gen) { int rt = ibo_data.Size(); int vi = vbo_shadowdata.Reserve(gen.vertices.Size()); @@ -175,7 +215,7 @@ int FFlatVertexGenerator::CreateIndexedSectorVertices(sector_t *sec, const secpl // //========================================================================== -int FFlatVertexGenerator::CreateIndexedVertices(int h, sector_t *sec, const secplane_t &plane, int floor, TArray &gen) +int FFlatVertexBuffer::CreateIndexedVertices(int h, sector_t *sec, const secplane_t &plane, int floor, TArray &gen) { sec->vboindex[h] = vbo_shadowdata.Size(); // First calculate the vertices for the sector itself @@ -221,7 +261,7 @@ int FFlatVertexGenerator::CreateIndexedVertices(int h, sector_t *sec, const secp // //========================================================================== -void FFlatVertexGenerator::CreateIndexedFlatVertices() +void FFlatVertexBuffer::CreateIndexedFlatVertices() { TArray gen; gen.Resize(level.sectors.Size()); @@ -270,13 +310,13 @@ void FFlatVertexGenerator::CreateIndexedFlatVertices() // //========================================================================== -void FFlatVertexGenerator::UpdatePlaneVertices(sector_t *sec, int plane) +void FFlatVertexBuffer::UpdatePlaneVertices(sector_t *sec, int plane) { int startvt = sec->vboindex[plane]; int countvt = sec->vbocount[plane]; secplane_t &splane = sec->GetSecPlane(plane); FFlatVertex *vt = &vbo_shadowdata[startvt]; - FFlatVertex *mapvt = &mMap[startvt]; + FFlatVertex *mapvt = GetBuffer(startvt); for(int i=0; iz = (float)splane.ZatPoint(vt->x, vt->y); @@ -291,7 +331,7 @@ void FFlatVertexGenerator::UpdatePlaneVertices(sector_t *sec, int plane) // //========================================================================== -void FFlatVertexGenerator::CreateVertices() +void FFlatVertexBuffer::CreateVertices() { vbo_shadowdata.Resize(NUM_RESERVED); CreateIndexedFlatVertices(); @@ -303,7 +343,7 @@ void FFlatVertexGenerator::CreateVertices() // //========================================================================== -void FFlatVertexGenerator::CheckPlanes(sector_t *sector) +void FFlatVertexBuffer::CheckPlanes(sector_t *sector) { if (sector->GetPlaneTexZ(sector_t::ceiling) != sector->vboheight[sector_t::ceiling]) { @@ -324,7 +364,7 @@ void FFlatVertexGenerator::CheckPlanes(sector_t *sector) // //========================================================================== -void FFlatVertexGenerator::CheckUpdate(sector_t *sector) +void FFlatVertexBuffer::CheckUpdate(sector_t *sector) { CheckPlanes(sector); sector_t *hs = sector->GetHeightSec(); @@ -332,3 +372,50 @@ void FFlatVertexGenerator::CheckUpdate(sector_t *sector) for (unsigned i = 0; i < sector->e->XFloor.ffloors.Size(); i++) CheckPlanes(sector->e->XFloor.ffloors[i]->model); } + +//========================================================================== +// +// +// +//========================================================================== + +std::pair FFlatVertexBuffer::AllocVertices(unsigned int count) +{ + FFlatVertex *p = GetBuffer(); + auto index = mCurIndex.fetch_add(count); + auto offset = index; + if (index + count >= BUFFER_SIZE_TO_USE) + { + // If a single scene needs 2'000'000 vertices there must be something very wrong. + I_FatalError("Out of vertex memory. Tried to allocate more than %u vertices for a single frame", index + count); + } + return std::make_pair(p, index); +} + +//========================================================================== +// +// +// +//========================================================================== + +void FFlatVertexBuffer::Copy(int start, int count) +{ + Map(); + memcpy(GetBuffer(start), &vbo_shadowdata[0], count * sizeof(FFlatVertex)); + Unmap(); +} + +//========================================================================== +// +// +// +//========================================================================== + +void FFlatVertexBuffer::CreateVBO() +{ + vbo_shadowdata.Resize(mNumReserved); + FFlatVertexBuffer::CreateVertices(); + mCurIndex = mIndex = vbo_shadowdata.Size(); + Copy(0, mIndex); + mIndexBuffer->SetData(ibo_data.Size() * sizeof(uint32_t), &ibo_data[0]); +} diff --git a/src/hwrenderer/data/flatvertices.h b/src/hwrenderer/data/flatvertices.h index 3025a6820..c55448379 100644 --- a/src/hwrenderer/data/flatvertices.h +++ b/src/hwrenderer/data/flatvertices.h @@ -24,6 +24,11 @@ #define _HW__VERTEXBUFFER_H #include "tarray.h" +#include "hwrenderer/data/buffers.h" +#include +#include + +class FRenderState; struct FFlatVertex { @@ -41,12 +46,22 @@ struct FFlatVertex } }; -class FFlatVertexGenerator +class FFlatVertexBuffer { -protected: TArray vbo_shadowdata; TArray ibo_data; - FFlatVertex *mMap; + + IVertexBuffer *mVertexBuffer; + IIndexBuffer *mIndexBuffer; + + unsigned int mIndex; + std::atomic mCurIndex; + unsigned int mNumReserved; + + + static const unsigned int BUFFER_SIZE = 2000000; + static const unsigned int BUFFER_SIZE_TO_USE = 1999500; + // Temporary data for creating an indexed buffer struct FIndexGenerationInfo @@ -84,9 +99,45 @@ public: NUM_RESERVED = 20 }; - FFlatVertexGenerator(int width, int height); + FFlatVertexBuffer(int width, int height); + ~FFlatVertexBuffer(); void OutputResized(int width, int height); + std::pair GetBufferObjects() const + { + return std::make_pair(mVertexBuffer, mIndexBuffer); + } + + void CreateVBO(); + void Copy(int start, int count); + + FFlatVertex *GetBuffer(int index) const + { + FFlatVertex *ff = (FFlatVertex*)mVertexBuffer->Memory(); + return &ff[index]; + } + + FFlatVertex *GetBuffer() const + { + return GetBuffer(mCurIndex); + } + + std::pair AllocVertices(unsigned int count); + + void Reset() + { + mCurIndex = mIndex; + } + + void Map() + { + mVertexBuffer->Map(); + } + + void Unmap() + { + mVertexBuffer->Unmap(); + } private: int CreateIndexedSubsectorVertices(subsector_t *sub, const secplane_t &plane, int floor, int vi, FIndexGenerationInfo &gen); @@ -100,6 +151,7 @@ protected: void CheckPlanes(sector_t *sector); public: void CheckUpdate(sector_t *sector); + }; #endif \ No newline at end of file diff --git a/src/hwrenderer/data/hw_viewpointbuffer.cpp b/src/hwrenderer/data/hw_viewpointbuffer.cpp new file mode 100644 index 000000000..da103063b --- /dev/null +++ b/src/hwrenderer/data/hw_viewpointbuffer.cpp @@ -0,0 +1,110 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_viewpointbuffer.cpp +** Buffer data maintenance for per viewpoint uniform data +** +**/ + +#include "hwrenderer/data/shaderuniforms.h" +#include "hwrenderer/scene/hw_viewpointuniforms.h" +#include "hwrenderer/scene/hw_drawinfo.h" +#include "hwrenderer/scene/hw_renderstate.h" +#include "hw_viewpointbuffer.h" + +static const int INITIAL_BUFFER_SIZE = 100; // 100 viewpoints per frame should nearly always be enough + +GLViewpointBuffer::GLViewpointBuffer() +{ + mBufferSize = INITIAL_BUFFER_SIZE; + mBlockAlign = ((sizeof(HWViewpointUniforms) / screen->uniformblockalignment) + 1) * screen->uniformblockalignment; + mByteSize = mBufferSize * mBlockAlign; + mBuffer = screen->CreateDataBuffer(VIEWPOINT_BINDINGPOINT, false); + mBuffer->SetData(mByteSize, nullptr, false); + Clear(); + mLastMappedIndex = UINT_MAX; + mClipPlaneInfo.Push(0); +} + +GLViewpointBuffer::~GLViewpointBuffer() +{ + delete mBuffer; +} + + +void GLViewpointBuffer::CheckSize() +{ + if (mUploadIndex >= mBufferSize) + { + mBufferSize *= 2; + mByteSize *= 2; + mBuffer->Resize(mByteSize); + } +} + +int GLViewpointBuffer::Bind(FRenderState &di, unsigned int index) +{ + if (index != mLastMappedIndex) + { + mLastMappedIndex = index; + mBuffer->BindRange(index * mBlockAlign, mBlockAlign); + di.EnableClipDistance(0, mClipPlaneInfo[index]); + } + return index; +} + +void GLViewpointBuffer::Set2D(FRenderState &di, int width, int height) +{ + if (width != m2DWidth || height != m2DHeight) + { + HWViewpointUniforms matrices; + matrices.SetDefaults(); + matrices.mProjectionMatrix.ortho(0, (float)width, (float)height, 0, -1.0f, 1.0f); + matrices.CalcDependencies(); + mBuffer->Map(); + memcpy(mBuffer->Memory(), &matrices, sizeof(matrices)); + mBuffer->Unmap(); + m2DWidth = width; + m2DHeight = height; + mLastMappedIndex = -1; + } + Bind(di, 0); +} + +int GLViewpointBuffer::SetViewpoint(FRenderState &di, HWViewpointUniforms *vp) +{ + CheckSize(); + mBuffer->Map(); + memcpy(((char*)mBuffer->Memory()) + mUploadIndex * mBlockAlign, vp, sizeof(*vp)); + mBuffer->Unmap(); + + mClipPlaneInfo.Push(vp->mClipHeightDirection != 0.f || vp->mClipLine.X > -10000000.0f); + return Bind(di, mUploadIndex++); +} + +void GLViewpointBuffer::Clear() +{ + // Index 0 is reserved for the 2D projection. + mUploadIndex = 1; + mClipPlaneInfo.Resize(1); +} + diff --git a/src/gl/data/gl_viewpointbuffer.h b/src/hwrenderer/data/hw_viewpointbuffer.h similarity index 60% rename from src/gl/data/gl_viewpointbuffer.h rename to src/hwrenderer/data/hw_viewpointbuffer.h index 3ec1093df..3a770ec91 100644 --- a/src/gl/data/gl_viewpointbuffer.h +++ b/src/hwrenderer/data/hw_viewpointbuffer.h @@ -1,36 +1,35 @@ #include "tarray.h" +#include "hwrenderer/data/buffers.h" struct HWViewpointUniforms; +class FRenderState; class GLViewpointBuffer { - unsigned int mBufferId; + IDataBuffer *mBuffer; + unsigned int mBufferSize; unsigned int mBlockAlign; unsigned int mUploadIndex; unsigned int mLastMappedIndex; unsigned int mByteSize; - void * mBufferPointer; TArray mClipPlaneInfo; - unsigned int m2DWidth = ~0u, m2DHeight = ~0u; + int m2DWidth = -1, m2DHeight = -1; unsigned int mBlockSize; void CheckSize(); - void Allocate(); public: GLViewpointBuffer(); ~GLViewpointBuffer(); void Clear(); - void Map(); - void Unmap(); - int Bind(unsigned int index); - void Set2D(int width, int height); - int SetViewpoint(HWViewpointUniforms *vp); + int Bind(FRenderState &di, unsigned int index); + void Set2D(FRenderState &di, int width, int height); + int SetViewpoint(FRenderState &di, HWViewpointUniforms *vp); unsigned int GetBlockSize() const { return mBlockSize; } }; diff --git a/src/hwrenderer/data/shaderuniforms.h b/src/hwrenderer/data/shaderuniforms.h index 4ec9c2393..765b64938 100644 --- a/src/hwrenderer/data/shaderuniforms.h +++ b/src/hwrenderer/data/shaderuniforms.h @@ -1,7 +1,7 @@ #pragma once #include -#include "hwrenderer/data/uniformbuffer.h" +#include "hwrenderer/data/buffers.h" #include "v_video.h" enum @@ -116,17 +116,17 @@ public: void Init() { if (mBuffer == nullptr) - mBuffer = screen->CreateUniformBuffer(sizeof(T)); + mBuffer = screen->CreateDataBuffer(bindingpoint, false); } void Set(bool bind = true) { if (mBuffer != nullptr) - mBuffer->SetData(&Values); + mBuffer->SetData(sizeof(T), &Values); // Let's hope this can be done better when things have moved further ahead. // This really is not the best place to add something that depends on API behavior. - if (bind) mBuffer->Bind(bindingpoint); + if (bind) mBuffer->BindBase(); } T *operator->() { return &Values; } @@ -138,7 +138,7 @@ private: ShaderUniforms(const ShaderUniforms &) = delete; ShaderUniforms &operator=(const ShaderUniforms &) = delete; - IUniformBuffer *mBuffer = nullptr; + IDataBuffer *mBuffer = nullptr; std::vector mFields; }; diff --git a/src/hwrenderer/data/uniformbuffer.h b/src/hwrenderer/data/uniformbuffer.h deleted file mode 100644 index 9aa078d3d..000000000 --- a/src/hwrenderer/data/uniformbuffer.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "zstring.h" - -class IUniformBuffer -{ -protected: - size_t mSize; - - IUniformBuffer(size_t size) : mSize(size) {} - -public: - virtual ~IUniformBuffer() {} - virtual void SetData(const void *data) = 0; - virtual void Bind(int bindingpoint) = 0; // This is only here for OpenGL. Vulkan doesn't need the ability to bind this at run time. - - -}; diff --git a/src/hwrenderer/dynlights/hw_lightbuffer.cpp b/src/hwrenderer/dynlights/hw_lightbuffer.cpp new file mode 100644 index 000000000..a9e24353c --- /dev/null +++ b/src/hwrenderer/dynlights/hw_lightbuffer.cpp @@ -0,0 +1,144 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2014-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_lightbuffer.cpp +** Buffer data maintenance for dynamic lights +** +**/ + +#include "hw_lightbuffer.h" +#include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/data/shaderuniforms.h" + +static const int ELEMENTS_PER_LIGHT = 4; // each light needs 4 vec4's. +static const int ELEMENT_SIZE = (4*sizeof(float)); + + +FLightBuffer::FLightBuffer() +{ + int maxNumberOfLights = 80000; + + mBufferSize = maxNumberOfLights * ELEMENTS_PER_LIGHT; + mByteSize = mBufferSize * ELEMENT_SIZE; + + // Hack alert: On Intel's GL driver SSBO's perform quite worse than UBOs. + // We only want to disable using SSBOs for lights but not disable the feature entirely. + // Note that using an uniform buffer here will limit the number of lights per surface so it isn't done for NVidia and AMD. + if (screen->hwcaps & RFL_SHADER_STORAGE_BUFFER && !strstr(screen->gl_vendorstring, "Intel")) + { + mBufferType = true; + mBlockAlign = 0; + mBlockSize = mBufferSize; + mMaxUploadSize = mBlockSize; + } + else + { + mBufferType = false; + mBlockSize = screen->maxuniformblock / ELEMENT_SIZE; + mBlockAlign = screen->uniformblockalignment / ELEMENT_SIZE; + mMaxUploadSize = (mBlockSize - mBlockAlign); + mByteSize += screen->maxuniformblock; // to avoid mapping beyond the end of the buffer. + } + + mBuffer = screen->CreateDataBuffer(LIGHTBUF_BINDINGPOINT, mBufferType); + mBuffer->SetData(mByteSize, nullptr, false); + + Clear(); + mLastMappedIndex = UINT_MAX; +} + +FLightBuffer::~FLightBuffer() +{ + delete mBuffer; +} + +void FLightBuffer::Clear() +{ + mIndex = 0; +} + +int FLightBuffer::UploadLights(FDynLightData &data) +{ + // All meaasurements here are in vec4's. + int size0 = data.arrays[0].Size()/4; + int size1 = data.arrays[1].Size()/4; + int size2 = data.arrays[2].Size()/4; + int totalsize = size0 + size1 + size2 + 1; + + if (totalsize > (int)mMaxUploadSize) + { + int diff = totalsize - (int)mMaxUploadSize; + + size2 -= diff; + if (size2 < 0) + { + size1 += size2; + size2 = 0; + } + if (size1 < 0) + { + size0 += size1; + size1 = 0; + } + totalsize = size0 + size1 + size2 + 1; + } + + float *mBufferPointer = (float*)mBuffer->Memory(); + assert(mBufferPointer != nullptr); + if (mBufferPointer == nullptr) return -1; + if (totalsize <= 1) return -1; // there are no lights + + unsigned thisindex = mIndex.fetch_add(totalsize); + float parmcnt[] = { 0, float(size0), float(size0 + size1), float(size0 + size1 + size2) }; + + if (thisindex + totalsize <= mBufferSize) + { + float *copyptr = mBufferPointer + thisindex*4; + + memcpy(©ptr[0], parmcnt, ELEMENT_SIZE); + memcpy(©ptr[4], &data.arrays[0][0], size0 * ELEMENT_SIZE); + memcpy(©ptr[4 + 4*size0], &data.arrays[1][0], size1 * ELEMENT_SIZE); + memcpy(©ptr[4 + 4*(size0 + size1)], &data.arrays[2][0], size2 * ELEMENT_SIZE); + return thisindex; + } + else + { + return -1; // Buffer is full. Since it is being used live at the point of the upload we cannot do much here but to abort. + } +} + +int FLightBuffer::DoBindUBO(unsigned int index) +{ + // this function will only get called if a uniform buffer is used. For a shader storage buffer we only need to bind the buffer once at the start. + unsigned int offset = (index / mBlockAlign) * mBlockAlign; + + if (offset != mLastMappedIndex) + { + mLastMappedIndex = offset; + mBuffer->BindRange(offset * ELEMENT_SIZE, mBlockSize * ELEMENT_SIZE); + } + return (index - offset); +} + + + diff --git a/src/hwrenderer/dynlights/hw_lightbuffer.h b/src/hwrenderer/dynlights/hw_lightbuffer.h new file mode 100644 index 000000000..9522ccc45 --- /dev/null +++ b/src/hwrenderer/dynlights/hw_lightbuffer.h @@ -0,0 +1,69 @@ +#ifndef __GL_LIGHTBUFFER_H +#define __GL_LIGHTBUFFER_H + +#include "tarray.h" +#include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/data/buffers.h" +#include +#include + +class FRenderState; + +class FLightBuffer +{ + IDataBuffer *mBuffer; + + bool mBufferType; + std::atomic mIndex; + unsigned int mLastMappedIndex; + unsigned int mBlockAlign; + unsigned int mBlockSize; + unsigned int mBufferSize; + unsigned int mByteSize; + unsigned int mMaxUploadSize; + + void CheckSize(); + +public: + + FLightBuffer(); + ~FLightBuffer(); + void Clear(); + int UploadLights(FDynLightData &data); + void Map() { mBuffer->Map(); } + void Unmap() { mBuffer->Unmap(); } + unsigned int GetBlockSize() const { return mBlockSize; } + bool GetBufferType() const { return mBufferType; } + + int DoBindUBO(unsigned int index); + + int ShaderIndex(unsigned int index) const + { + if (mBlockAlign == 0) return index; + // This must match the math in BindUBO. + unsigned int offset = (index / mBlockAlign) * mBlockAlign; + return int(index-offset); + } + + // Only relevant for OpenGL, so this does not need access to the render state. + int BindUBO(int index) + { + if (!mBufferType && index > -1) + { + index = DoBindUBO(index); + } + return index; + } + + // The parameter is a reminder for Vulkan. + void BindBase() + { + mBuffer->BindBase(); + } + +}; + +int gl_SetDynModelLight(AActor *self, int dynlightindex); + +#endif + diff --git a/src/hwrenderer/dynlights/hw_shadowmap.cpp b/src/hwrenderer/dynlights/hw_shadowmap.cpp index 027942f46..024a5cc97 100644 --- a/src/hwrenderer/dynlights/hw_shadowmap.cpp +++ b/src/hwrenderer/dynlights/hw_shadowmap.cpp @@ -23,6 +23,7 @@ #include "hwrenderer/dynlights/hw_shadowmap.h" #include "hwrenderer/utility/hw_cvars.h" #include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/data/buffers.h" #include "stats.h" #include "g_levellocals.h" @@ -160,3 +161,54 @@ bool IShadowMap::ValidateAABBTree() mAABBTree.reset(new hwrenderer::LevelAABBTree()); return false; } + +bool IShadowMap::PerformUpdate() +{ + UpdateCycles.Reset(); + + LightsProcessed = 0; + LightsShadowmapped = 0; + + if (IsEnabled()) + { + UpdateCycles.Clock(); + UploadAABBTree(); + UploadLights(); + mLightList->BindBase(); + mNodesBuffer->BindBase(); + mLinesBuffer->BindBase(); + return true; + } + return false; +} + +void IShadowMap::UploadLights() +{ + CollectLights(); + + if (mLightList == nullptr) + mLightList = screen->CreateDataBuffer(4, true); + + mLightList->SetData(sizeof(float) * mLights.Size(), &mLights[0]); +} + + +void IShadowMap::UploadAABBTree() +{ + if (!ValidateAABBTree()) + { + mNodesBuffer = screen->CreateDataBuffer(2, true); + mNodesBuffer->SetData(sizeof(hwrenderer::AABBTreeNode) * mAABBTree->nodes.Size(), &mAABBTree->nodes[0]); + + mLinesBuffer = screen->CreateDataBuffer(3, true); + mLinesBuffer->SetData(sizeof(hwrenderer::AABBTreeLine) * mAABBTree->lines.Size(), &mAABBTree->lines[0]); + } +} + +IShadowMap::~IShadowMap() +{ + if (mLightList) delete mLightList; + if (mNodesBuffer) delete mNodesBuffer; + if (mLinesBuffer) delete mLinesBuffer; +} + diff --git a/src/hwrenderer/dynlights/hw_shadowmap.h b/src/hwrenderer/dynlights/hw_shadowmap.h index 492993919..8c004b559 100644 --- a/src/hwrenderer/dynlights/hw_shadowmap.h +++ b/src/hwrenderer/dynlights/hw_shadowmap.h @@ -7,18 +7,13 @@ class ADynamicLight; struct level_info_t; +class IDataBuffer; class IShadowMap { public: IShadowMap() { } - virtual ~IShadowMap() { } - - // Release resources - virtual void Clear() = 0; - - // Update shadow map texture - virtual void Update() = 0; + virtual ~IShadowMap(); // Test if a world position is in shadow relative to the specified light and returns false if it is bool ShadowTest(ADynamicLight *light, const DVector3 &pos); @@ -29,11 +24,23 @@ public: static cycle_t UpdateCycles; static int LightsProcessed; static int LightsShadowmapped; - + + bool PerformUpdate(); + void FinishUpdate() + { + UpdateCycles.Clock(); + } + protected: void CollectLights(); bool ValidateAABBTree(); + // Upload the AABB-tree to the GPU + void UploadAABBTree(); + + // Upload light list to the GPU + void UploadLights(); + // Working buffer for creating the list of lights. Stored here to avoid allocating memory each frame TArray mLights; @@ -47,4 +54,12 @@ protected: IShadowMap(const IShadowMap &) = delete; IShadowMap &operator=(IShadowMap &) = delete; + + // OpenGL storage buffer with the list of lights in the shadow map texture + IDataBuffer *mLightList = nullptr; + + // OpenGL storage buffers for the AABB tree + IDataBuffer *mNodesBuffer = nullptr; + IDataBuffer *mLinesBuffer = nullptr; + }; diff --git a/src/hwrenderer/models/hw_models.cpp b/src/hwrenderer/models/hw_models.cpp new file mode 100644 index 000000000..ce22625d8 --- /dev/null +++ b/src/hwrenderer/models/hw_models.cpp @@ -0,0 +1,221 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2005-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_models.cpp +** +** hardware renderer model handling code +** +**/ + +#include "w_wad.h" +#include "g_game.h" +#include "doomstat.h" +#include "g_level.h" +#include "r_state.h" +#include "d_player.h" +#include "g_levellocals.h" +#include "i_time.h" +#include "cmdlib.h" +#include "hwrenderer/textures/hw_material.h" +#include "hwrenderer/data/buffers.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/scene/hw_drawinfo.h" +#include "hwrenderer/scene/hw_renderstate.h" +#include "hwrenderer/scene/hw_portal.h" +#include "hw_models.h" + +CVAR(Bool, gl_light_models, true, CVAR_ARCHIVE) + +VSMatrix FGLModelRenderer::GetViewToWorldMatrix() +{ + VSMatrix objectToWorldMatrix; + di->VPUniforms.mViewMatrix.inverseMatrix(objectToWorldMatrix); + return objectToWorldMatrix; +} + +void FGLModelRenderer::BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) +{ + state.SetDepthFunc(DF_LEqual); + state.EnableTexture(true); + // [BB] In case the model should be rendered translucent, do back face culling. + // This solves a few of the problems caused by the lack of depth sorting. + // [Nash] Don't do back face culling if explicitly specified in MODELDEF + // TO-DO: Implement proper depth sorting. + if (!(actor->RenderStyle == DefaultRenderStyle()) && !(smf->flags & MDL_DONTCULLBACKFACES)) + { + state.SetCulling((mirrored ^ screen->mPortalState->isMirrored()) ? Cull_CCW : Cull_CW); + } + + state.mModelMatrix = objectToWorldMatrix; + state.EnableModelMatrix(true); +} + +void FGLModelRenderer::EndDrawModel(AActor *actor, FSpriteModelFrame *smf) +{ + state.EnableModelMatrix(false); + state.SetDepthFunc(DF_Less); + if (!(actor->RenderStyle == DefaultRenderStyle()) && !(smf->flags & MDL_DONTCULLBACKFACES)) + state.SetCulling(Cull_None); +} + +void FGLModelRenderer::BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) +{ + state.SetDepthFunc(DF_LEqual); + + // [BB] In case the model should be rendered translucent, do back face culling. + // This solves a few of the problems caused by the lack of depth sorting. + // TO-DO: Implement proper depth sorting. + if (!(actor->RenderStyle == DefaultRenderStyle())) + { + state.SetCulling((mirrored ^ screen->mPortalState->isMirrored()) ? Cull_CW : Cull_CCW); + } + + state.mModelMatrix = objectToWorldMatrix; + state.EnableModelMatrix(true); +} + +void FGLModelRenderer::EndDrawHUDModel(AActor *actor) +{ + state.EnableModelMatrix(false); + + state.SetDepthFunc(DF_Less); + if (!(actor->RenderStyle == DefaultRenderStyle())) + state.SetCulling(Cull_None); +} + +IModelVertexBuffer *FGLModelRenderer::CreateVertexBuffer(bool needindex, bool singleframe) +{ + return new FModelVertexBuffer(needindex, singleframe); +} + +void FGLModelRenderer::SetInterpolation(double inter) +{ + state.SetInterpolationFactor((float)inter); +} + +void FGLModelRenderer::SetMaterial(FTexture *skin, bool clampNoFilter, int translation) +{ + FMaterial * tex = FMaterial::ValidateTexture(skin, false); + state.SetMaterial(tex, clampNoFilter ? CLAMP_NOFILTER : CLAMP_NONE, translation, -1); + state.SetLightIndex(modellightindex); +} + +void FGLModelRenderer::DrawArrays(int start, int count) +{ + state.Draw(DT_Triangles, start, count); +} + +void FGLModelRenderer::DrawElements(int numIndices, size_t offset) +{ + state.DrawIndexed(DT_Triangles, int(offset / sizeof(unsigned int)), numIndices); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +FModelVertexBuffer::FModelVertexBuffer(bool needindex, bool singleframe) +{ + mVertexBuffer = screen->CreateVertexBuffer(); + mIndexBuffer = needindex ? screen->CreateIndexBuffer() : nullptr; + + static const FVertexBufferAttribute format[] = { + { 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(FModelVertex, x) }, + { 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FModelVertex, u) }, + { 0, VATTR_NORMAL, VFmt_Packed_A2R10G10B10, (int)myoffsetof(FModelVertex, packedNormal) }, + { 1, VATTR_VERTEX2, VFmt_Float3, (int)myoffsetof(FModelVertex, x) } + }; + mVertexBuffer->SetFormat(2, 4, sizeof(FModelVertex), format); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +FModelVertexBuffer::~FModelVertexBuffer() +{ + if (mIndexBuffer) delete mIndexBuffer; + delete mVertexBuffer; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +FModelVertex *FModelVertexBuffer::LockVertexBuffer(unsigned int size) +{ + return static_cast(mVertexBuffer->Lock(size * sizeof(FModelVertex))); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FModelVertexBuffer::UnlockVertexBuffer() +{ + mVertexBuffer->Unlock(); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +unsigned int *FModelVertexBuffer::LockIndexBuffer(unsigned int size) +{ + if (mIndexBuffer) return static_cast(mIndexBuffer->Lock(size * sizeof(unsigned int))); + else return nullptr; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FModelVertexBuffer::UnlockIndexBuffer() +{ + if (mIndexBuffer) mIndexBuffer->Unlock(); +} + + +//=========================================================================== +// +// +// +//=========================================================================== + +void FModelVertexBuffer::SetupFrame(FModelRenderer *renderer, unsigned int frame1, unsigned int frame2, unsigned int size) +{ + auto &state = static_cast(renderer)->state; + state.SetVertexBuffer(mVertexBuffer, frame1, frame2); + if (mIndexBuffer) state.SetIndexBuffer(mIndexBuffer); +} diff --git a/src/gl/models/gl_models.h b/src/hwrenderer/models/hw_models.h similarity index 72% rename from src/gl/models/gl_models.h rename to src/hwrenderer/models/hw_models.h index fb044c49f..4d2086acf 100644 --- a/src/gl/models/gl_models.h +++ b/src/hwrenderer/models/hw_models.h @@ -23,27 +23,46 @@ #pragma once #include "tarray.h" -#include "gl/data/gl_vertexbuffer.h" #include "p_pspr.h" #include "r_data/voxels.h" #include "r_data/models/models.h" class GLSprite; -struct FDrawInfo; +struct HWDrawInfo; +class FRenderState; + +class FModelVertexBuffer : public IModelVertexBuffer +{ + IVertexBuffer *mVertexBuffer; + IIndexBuffer *mIndexBuffer; + +public: + + FModelVertexBuffer(bool needindex, bool singleframe); + ~FModelVertexBuffer(); + + FModelVertex *LockVertexBuffer(unsigned int size) override; + void UnlockVertexBuffer() override; + + unsigned int *LockIndexBuffer(unsigned int size) override; + void UnlockIndexBuffer() override; + + void SetupFrame(FModelRenderer *renderer, unsigned int frame1, unsigned int frame2, unsigned int size) override; +}; class FGLModelRenderer : public FModelRenderer { + friend class FModelVertexBuffer; int modellightindex = -1; - FDrawInfo *di; + HWDrawInfo *di; + FRenderState &state; public: - FGLModelRenderer(FDrawInfo *d, int mli) : modellightindex(mli), di(d) + FGLModelRenderer(HWDrawInfo *d, FRenderState &st, int mli) : modellightindex(mli), di(d), state(st) {} ModelRendererType GetType() const override { return GLModelRendererType; } void BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) override; void EndDrawModel(AActor *actor, FSpriteModelFrame *smf) override; IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) override; - void SetVertexBuffer(IModelVertexBuffer *buffer) override; - void ResetVertexBuffer() override; VSMatrix GetViewToWorldMatrix() override; void BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) override; void EndDrawHUDModel(AActor *actor) override; diff --git a/src/hwrenderer/scene/hw_bsp.cpp b/src/hwrenderer/scene/hw_bsp.cpp index edbe04dc3..75be620f4 100644 --- a/src/hwrenderer/scene/hw_bsp.cpp +++ b/src/hwrenderer/scene/hw_bsp.cpp @@ -31,6 +31,8 @@ #include "g_levellocals.h" #include "p_effect.h" #include "po_man.h" +#include "m_fixed.h" +#include "ctpl.h" #include "hwrenderer/scene/hw_fakeflat.h" #include "hwrenderer/scene/hw_clipper.h" #include "hwrenderer/scene/hw_drawstructs.h" @@ -38,6 +40,146 @@ #include "hwrenderer/scene/hw_portal.h" #include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/data/flatvertices.h" +#include + +CVAR(Bool, gl_multithread, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +thread_local bool isWorkerThread; +ctpl::thread_pool renderPool(1); +bool inited = false; + +struct RenderJob +{ + enum + { + FlatJob, + WallJob, + SpriteJob, + ParticleJob, + TerminateJob // inserted when all work is done so that the worker can return. + }; + + int type; + subsector_t *sub; + seg_t *seg; +}; + + +class RenderJobQueue +{ + RenderJob pool[300000]; // Way more than ever needed. The largest ever seen on a single viewpoint is around 40000. + std::atomic readindex{}; + std::atomic writeindex{}; +public: + void AddJob(int type, subsector_t *sub, seg_t *seg = nullptr) + { + // This does not check for array overflows. The pool should be large enough that it never hits the limit. + + pool[writeindex] = { type, sub, seg }; + writeindex++; // update index only after the value has been written. + } + + RenderJob *GetJob() + { + if (readindex < writeindex) return &pool[readindex++]; + return nullptr; + } + + void ReleaseAll() + { + readindex = 0; + writeindex = 0; + } +}; + +static RenderJobQueue jobQueue; // One static queue is sufficient here. This code will never be called recursively. + +void HWDrawInfo::WorkerThread() +{ + sector_t fakefront, fakeback, *front, *back; + + WTTotal.Clock(); + isWorkerThread = true; // for adding asserts in GL API code. The worker thread may never call any GL API. + while (true) + { + auto job = jobQueue.GetJob(); + if (job == nullptr) + { + // The queue is empty. But yielding would be too costly here and possibly cause further delays down the line if the thread is halted. + // So instead add a few pause instructions and retry immediately. + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + _mm_pause(); + } + else switch (job->type) + { + case RenderJob::TerminateJob: + WTTotal.Unclock(); + return; + + case RenderJob::WallJob: + { + GLWall wall; + SetupWall.Clock(); + wall.sub = job->sub; + + front = hw_FakeFlat(job->sub->sector, &fakefront, in_area, false); + auto seg = job->seg; + if (seg->backsector) + { + if (front->sectornum == seg->backsector->sectornum || (seg->sidedef->Flags & WALLF_POLYOBJ)) + { + back = front; + } + else + { + back = hw_FakeFlat(seg->backsector, &fakeback, in_area, true); + } + } + else back = nullptr; + + wall.Process(this, job->seg, front, back); + rendered_lines++; + SetupWall.Unclock(); + break; + } + + case RenderJob::FlatJob: + { + GLFlat flat; + SetupFlat.Clock(); + front = hw_FakeFlat(job->sub->render_sector, &fakefront, in_area, false); + flat.ProcessSector(this, front); + SetupFlat.Unclock(); + break; + } + + case RenderJob::SpriteJob: + SetupSprite.Clock(); + front = hw_FakeFlat(job->sub->sector, &fakefront, in_area, false); + RenderThings(job->sub, front); + SetupSprite.Unclock(); + break; + + case RenderJob::ParticleJob: + SetupSprite.Clock(); + front = hw_FakeFlat(job->sub->sector, &fakefront, in_area, false); + RenderParticles(job->sub, front); + SetupSprite.Unclock(); + break; + } + } +} + + + EXTERN_CVAR(Bool, gl_render_segs) @@ -170,14 +312,19 @@ void HWDrawInfo::AddLine (seg_t *seg, bool portalclip) if (gl_render_walls) { - SetupWall.Clock(); - - GLWall wall; - wall.sub = currentsubsector; - wall.Process(this, seg, currentsector, backsector); - rendered_lines++; - - SetupWall.Unclock(); + if (multithread) + { + jobQueue.AddJob(RenderJob::WallJob, seg->Subsector, seg); + } + else + { + GLWall wall; + SetupWall.Clock(); + wall.sub = seg->Subsector; + wall.Process(this, seg, currentsector, backsector); + rendered_lines++; + SetupWall.Unclock(); + } } } } @@ -350,7 +497,6 @@ void HWDrawInfo::AddSpecialPortalLines(subsector_t * sub, sector_t * sector, lin void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector) { - SetupSprite.Clock(); sector_t * sec=sub->sector; // Handle all things in sector. const auto &vp = Viewpoint; @@ -395,6 +541,22 @@ void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector) GLSprite sprite; sprite.Process(this, thing, sector, in_area, true); } +} + +void HWDrawInfo::RenderParticles(subsector_t *sub, sector_t *front) +{ + SetupSprite.Clock(); + for (int i = ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Particles[i].snext) + { + if (mClipPortal) + { + int clipres = mClipPortal->ClipPoint(Particles[i].Pos); + if (clipres == PClip_InFront) continue; + } + + GLSprite sprite; + sprite.ProcessParticle(this, &Particles[i], front); + } SetupSprite.Unclock(); } @@ -410,7 +572,6 @@ void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector) void HWDrawInfo::DoSubsector(subsector_t * sub) { - unsigned int i; sector_t * sector; sector_t * fakesector; sector_t fake; @@ -454,31 +615,22 @@ void HWDrawInfo::DoSubsector(subsector_t * sub) if (sector->validcount != validcount) { - if (sector->Index() == 62) - { - int a = 0; - } - mVBO->CheckUpdate(sector); + screen->mVertexData->CheckUpdate(sector); } // [RH] Add particles - //int shade = LIGHT2SHADE((floorlightlevel + ceilinglightlevel)/2 + r_actualextralight); - if (gl_render_things) + if (gl_render_things && ParticlesInSubsec[sub->Index()] != NO_PARTICLE) { - SetupSprite.Clock(); - - for (i = ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Particles[i].snext) + if (multithread) { - if (mClipPortal) - { - int clipres = mClipPortal->ClipPoint(Particles[i].Pos); - if (clipres == PClip_InFront) continue; - } - - GLSprite sprite; - sprite.ProcessParticle(this, &Particles[i], fakesector); + jobQueue.AddJob(RenderJob::ParticleJob, sub, nullptr); + } + else + { + SetupSprite.Clock(); + RenderParticles(sub, fakesector); + SetupSprite.Unclock(); } - SetupSprite.Unclock(); } AddLines(sub, fakesector); @@ -491,12 +643,21 @@ void HWDrawInfo::DoSubsector(subsector_t * sub) { // Well, now it will be done. sector->validcount = validcount; - - if (gl_render_things) - { - RenderThings(sub, fakesector); - } sector->MoreFlags |= SECMF_DRAWN; + + if (gl_render_things && sector->touching_renderthings) + { + if (multithread) + { + jobQueue.AddJob(RenderJob::SpriteJob, sub, nullptr); + } + else + { + SetupSprite.Clock(); + RenderThings(sub, fakesector); + SetupSprite.Unclock(); + } + } } if (gl_render_flats) @@ -523,10 +684,17 @@ void HWDrawInfo::DoSubsector(subsector_t * sub) { srf |= SSRF_PROCESSED; - SetupFlat.Clock(); - GLFlat flat; - flat.ProcessSector(this, fakesector); - SetupFlat.Unclock(); + if (multithread) + { + jobQueue.AddJob(RenderJob::FlatJob, sub); + } + else + { + GLFlat flat; + SetupFlat.Clock(); + flat.ProcessSector(this, fakesector); + SetupFlat.Unclock(); + } } // mark subsector as processed - but mark for rendering only if it has an actual area. ss_renderflags[sub->Index()] = @@ -595,4 +763,39 @@ void HWDrawInfo::RenderBSPNode (void *node) DoSubsector ((subsector_t *)((uint8_t *)node - 1)); } +void HWDrawInfo::RenderBSP(void *node) +{ + Bsp.Clock(); + // Give the DrawInfo the viewpoint in fixed point because that's what the nodes are. + viewx = FLOAT2FIXED(Viewpoint.Pos.X); + viewy = FLOAT2FIXED(Viewpoint.Pos.Y); + + validcount++; // used for processing sidedefs only once by the renderer. + + multithread = gl_multithread; + if (multithread) + { + jobQueue.ReleaseAll(); + auto future = renderPool.push([&](int id) { + WorkerThread(); + }); + RenderBSPNode(node); + + jobQueue.AddJob(RenderJob::TerminateJob, nullptr, nullptr); + Bsp.Unclock(); + MTWait.Clock(); + future.wait(); + MTWait.Unclock(); + } + else + { + RenderBSPNode(node); + Bsp.Unclock(); + } + // Process all the sprites on the current portal's back side which touch the portal. + if (mCurrentPortal != nullptr) mCurrentPortal->RenderAttached(this); + + + PreparePlayerSprites(Viewpoint.sector, in_area); +} diff --git a/src/hwrenderer/scene/hw_decal.cpp b/src/hwrenderer/scene/hw_decal.cpp index f9c59a79f..5a3e509bf 100644 --- a/src/hwrenderer/scene/hw_decal.cpp +++ b/src/hwrenderer/scene/hw_decal.cpp @@ -34,7 +34,159 @@ #include "hwrenderer/scene/hw_drawstructs.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "hwrenderer/utility/hw_lighting.h" +#include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/data/flatvertices.h" +#include "hw_renderstate.h" + +//========================================================================== +// +// +// +//========================================================================== + +void GLDecal::DrawDecal(HWDrawInfo *di, FRenderState &state) +{ + auto tex = gltexture; + + // calculate dynamic light effect. + if (level.HasDynamicLights && !di->isFullbrightScene() && gl_light_sprites) + { + // Note: This should be replaced with proper shader based lighting. + double x, y; + float out[3]; + decal->GetXY(decal->Side, x, y); + di->GetDynSpriteLight(nullptr, x, y, zcenter, decal->Side->lighthead, decal->Side->sector->PortalGroup, out); + state.SetDynLight(out[0], out[1], out[2]); + } + + // alpha color only has an effect when using an alpha texture. + if (decal->RenderStyle.Flags & (STYLEF_RedIsAlpha | STYLEF_ColorIsFixed)) + { + state.SetObjectColor(decal->AlphaColor | 0xff000000); + } + + state.SetTextureMode(decal->RenderStyle); + state.SetRenderStyle(decal->RenderStyle); + state.SetMaterial(tex, CLAMP_XY, decal->Translation, -1); + + + // If srcalpha is one it looks better with a higher alpha threshold + if (decal->RenderStyle.SrcAlpha == STYLEALPHA_One) state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); + else state.AlphaFunc(Alpha_Greater, 0.f); + + + state.SetColor(lightlevel, rellight, di->isFullbrightScene(), Colormap, alpha); + // for additively drawn decals we must temporarily set the fog color to black. + PalEntry fc = state.GetFogColor(); + if (decal->RenderStyle.BlendOp == STYLEOP_Add && decal->RenderStyle.DestAlpha == STYLEALPHA_One) + { + state.SetFog(0, -1); + } + + state.SetNormal(Normal); + + if (lightlist == nullptr) + { + state.Draw(DT_TriangleFan, vertindex, 4); + } + else + { + auto &lightlist = *this->lightlist; + + for (unsigned k = 0; k < lightlist.Size(); k++) + { + secplane_t &lowplane = k == lightlist.Size() - 1 ? bottomplane : lightlist[k + 1].plane; + + float low1 = lowplane.ZatPoint(dv[1].x, dv[1].y); + float low2 = lowplane.ZatPoint(dv[2].x, dv[2].y); + + if (low1 < dv[1].z || low2 < dv[2].z) + { + int thisll = lightlist[k].caster != nullptr ? hw_ClampLight(*lightlist[k].p_lightlevel) : lightlevel; + FColormap thiscm; + thiscm.FadeColor = Colormap.FadeColor; + thiscm.CopyFrom3DLight(&lightlist[k]); + state.SetColor(thisll, rellight, di->isFullbrightScene(), thiscm, alpha); + if (level.flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) thiscm.Decolorize(); + state.SetFog(thisll, rellight, di->isFullbrightScene(), &thiscm, false); + state.SetSplitPlanes(lightlist[k].plane, lowplane); + + state.Draw(DT_TriangleFan, vertindex, 4); + } + if (low1 <= dv[0].z && low2 <= dv[3].z) break; + } + } + + rendered_decals++; + state.SetTextureMode(TM_NORMAL); + state.SetObjectColor(0xffffffff); + state.SetFog(fc, -1); + state.SetDynLight(0, 0, 0); +} + +//========================================================================== +// +// +// +//========================================================================== +void HWDrawInfo::DrawDecals(FRenderState &state, TArray &decals) +{ + side_t *wall = nullptr; + state.SetDepthMask(false); + state.SetDepthBias(-1, -128); + state.SetLightIndex(-1); + for (auto gldecal : decals) + { + if (gldecal->decal->Side != wall) + { + wall = gldecal->decal->Side; + if (gldecal->lightlist != nullptr) + { + state.EnableSplit(true); + } + else + { + state.EnableSplit(false); + state.SetFog(gldecal->lightlevel, gldecal->rellight, isFullbrightScene(), &gldecal->Colormap, false); + } + } + gldecal->DrawDecal(this, state); + } + state.EnableSplit(false); + state.ClearDepthBias(); + state.SetTextureMode(TM_NORMAL); + state.SetDepthMask(true); +} + +//========================================================================== +// +// This list will never get long, so this code should be ok. +// +//========================================================================== + +void GLWall::DrawDecalsForMirror(HWDrawInfo *di, FRenderState &state, TArray &decals) +{ + state.SetDepthMask(false); + state.SetDepthBias(-1, -128); + state.SetLightIndex(-1); + state.SetFog(lightlevel, rellight + getExtraLight(), di->isFullbrightScene(), &Colormap, false); + for (auto gldecal : decals) + { + if (gldecal->decal->Side == seg->sidedef) + { + gldecal->DrawDecal(di, state); + } + } + state.ClearDepthBias(); + state.SetTextureMode(TM_NORMAL); + state.SetDepthMask(true); +} + +//========================================================================== +// +// +// +//========================================================================== void GLWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &normal) { @@ -257,7 +409,7 @@ void GLWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &nor gldecal->lightlist = lightlist; memcpy(gldecal->dv, dv, sizeof(dv)); - auto verts = di->AllocVertices(4); + auto verts = screen->mVertexData->AllocVertices(4); gldecal->vertindex = verts.second; for (i = 0; i < 4; i++) @@ -271,6 +423,7 @@ void GLWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &nor // // //========================================================================== + void GLWall::ProcessDecals(HWDrawInfo *di) { if (seg->sidedef != nullptr) diff --git a/src/hwrenderer/scene/hw_drawinfo.cpp b/src/hwrenderer/scene/hw_drawinfo.cpp index c72f510ae..d781fc64d 100644 --- a/src/hwrenderer/scene/hw_drawinfo.cpp +++ b/src/hwrenderer/scene/hw_drawinfo.cpp @@ -31,16 +31,155 @@ #include "d_player.h" #include "g_levellocals.h" #include "hw_fakeflat.h" -#include "hw_drawinfo.h" #include "hw_portal.h" +#include "hw_renderstate.h" +#include "hw_drawinfo.h" +#include "po_man.h" +#include "r_data/models/models.h" #include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/utility/hw_cvars.h" +#include "hwrenderer/data/hw_viewpointbuffer.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hwrenderer/utility/hw_vrmodes.h" +#include "hw_clipper.h" EXTERN_CVAR(Float, r_visibility) CVAR(Bool, gl_bandedswlight, false, CVAR_ARCHIVE) +CVAR(Bool, gl_sort_textures, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) sector_t * hw_FakeFlat(sector_t * sec, sector_t * dest, area_t in_area, bool back); +//========================================================================== +// +// +// +//========================================================================== + +template +inline void DeleteLinkedList(T *node) +{ + while (node) + { + auto n = node; + node = node->next; + delete n; + } +} + + +class FDrawInfoList +{ +public: + TDeletingArray mList; + + HWDrawInfo * GetNew(); + void Release(HWDrawInfo *); +}; + + +FDrawInfoList di_list; + +//========================================================================== +// +// Try to reuse the lists as often as possible as they contain resources that +// are expensive to create and delete. +// +// Note: If multithreading gets used, this class needs synchronization. +// +//========================================================================== + +HWDrawInfo *FDrawInfoList::GetNew() +{ + if (mList.Size() > 0) + { + HWDrawInfo *di; + mList.Pop(di); + return di; + } + return new HWDrawInfo; +} + +void FDrawInfoList::Release(HWDrawInfo * di) +{ + di->DrawScene = nullptr; + di->ClearBuffers(); + mList.Push(di); +} + +//========================================================================== +// +// Sets up a new drawinfo struct +// +//========================================================================== + +HWDrawInfo *HWDrawInfo::StartDrawInfo(HWDrawInfo *parent, FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms) +{ + HWDrawInfo *di = di_list.GetNew(); + if (parent) di->DrawScene = parent->DrawScene; + di->StartScene(parentvp, uniforms); + return di; +} + + +//========================================================================== +// +// +// +//========================================================================== + +static Clipper staticClipper; // Since all scenes are processed sequentially we only need one clipper. +static HWDrawInfo * gl_drawinfo; // This is a linked list of all active DrawInfos and needed to free the memory arena after the last one goes out of scope. + +void HWDrawInfo::StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms) +{ + staticClipper.Clear(); + mClipper = &staticClipper; + + Viewpoint = parentvp; + if (uniforms) + { + VPUniforms = *uniforms; + // The clip planes will never be inherited from the parent drawinfo. + VPUniforms.mClipLine.X = -1000001.f; + VPUniforms.mClipHeight = 0; + } + else VPUniforms.SetDefaults(); + mClipper->SetViewpoint(Viewpoint); + + ClearBuffers(); + + for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset(); + hudsprites.Clear(); + vpIndex = 0; + + // Fullbright information needs to be propagated from the main view. + if (outer != nullptr) FullbrightFlags = outer->FullbrightFlags; + else FullbrightFlags = 0; + + outer = gl_drawinfo; + gl_drawinfo = this; + +} + +//========================================================================== +// +// +// +//========================================================================== + +HWDrawInfo *HWDrawInfo::EndDrawInfo() +{ + assert(this == gl_drawinfo); + for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset(); + gl_drawinfo = outer; + di_list.Release(this); + if (gl_drawinfo == nullptr) + ResetRenderDataAllocator(); + return gl_drawinfo; +} + + //========================================================================== // // @@ -49,30 +188,18 @@ sector_t * hw_FakeFlat(sector_t * sec, sector_t * dest, area_t in_area, bool bac void HWDrawInfo::ClearBuffers() { - for(unsigned int i=0;i< otherfloorplanes.Size();i++) - { - gl_subsectorrendernode * node = otherfloorplanes[i]; - while (node) - { - gl_subsectorrendernode * n = node; - node = node->next; - delete n; - } - } + for (auto node : otherfloorplanes) DeleteLinkedList(node); otherfloorplanes.Clear(); - for(unsigned int i=0;i< otherceilingplanes.Size();i++) - { - gl_subsectorrendernode * node = otherceilingplanes[i]; - while (node) - { - gl_subsectorrendernode * n = node; - node = node->next; - delete n; - } - } + for (auto node : otherceilingplanes) DeleteLinkedList(node); otherceilingplanes.Clear(); + for (auto node : floodfloorsegs) DeleteLinkedList(node); + floodfloorsegs.Clear(); + + for (auto node : floodceilingsegs) DeleteLinkedList(node); + floodceilingsegs.Clear(); + // clear all the lists that might not have been cleared already MissingUpperTextures.Clear(); MissingLowerTextures.Clear(); @@ -95,6 +222,9 @@ void HWDrawInfo::ClearBuffers() memset(&ss_renderflags[0], 0, level.subsectors.Size() * sizeof(ss_renderflags[0])); memset(&no_renderflags[0], 0, level.nodes.Size() * sizeof(no_renderflags[0])); + Decals[0].Clear(); + Decals[1].Clear(); + mClipPortal = nullptr; mCurrentPortal = nullptr; } @@ -242,13 +372,14 @@ void HWDrawInfo::SetViewMatrix(const FRotator &angles, float vx, float vy, float // Setup the view rotation matrix for the given viewpoint // //----------------------------------------------------------------------------- -void HWDrawInfo::SetupView(float vx, float vy, float vz, bool mirror, bool planemirror) +void HWDrawInfo::SetupView(FRenderState &state, float vx, float vy, float vz, bool mirror, bool planemirror) { auto &vp = Viewpoint; vp.SetViewAngle(r_viewwindow); SetViewMatrix(vp.HWAngles, vx, vy, vz, mirror, planemirror); SetCameraPos(vp.Pos); - ApplyVPUniforms(); + VPUniforms.CalcDependencies(); + vpIndex = screen->mViewpoints->SetViewpoint(state, &VPUniforms); } //----------------------------------------------------------------------------- @@ -257,7 +388,7 @@ void HWDrawInfo::SetupView(float vx, float vy, float vz, bool mirror, bool plane // //----------------------------------------------------------------------------- -IPortal * HWDrawInfo::FindPortal(const void * src) +HWPortal * HWDrawInfo::FindPortal(const void * src) { int i = Portals.Size() - 1; @@ -283,3 +414,263 @@ void HWViewpointUniforms::SetDefaults() mShadowmapFilter = gl_shadowmap_filter; } + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +GLDecal *HWDrawInfo::AddDecal(bool onmirror) +{ + auto decal = (GLDecal*)RenderDataAllocator.Alloc(sizeof(GLDecal)); + Decals[onmirror ? 1 : 0].Push(decal); + return decal; +} + +//----------------------------------------------------------------------------- +// +// CreateScene +// +// creates the draw lists for the current scene +// +//----------------------------------------------------------------------------- + +void HWDrawInfo::CreateScene() +{ + const auto &vp = Viewpoint; + angle_t a1 = FrustumAngle(); + mClipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs() + a1, vp.Angles.Yaw.BAMs() - a1); + + // reset the portal manager + screen->mPortalState->StartFrame(); + PO_LinkToSubsectors(); + + ProcessAll.Clock(); + + // clip the scene and fill the drawlists + screen->mVertexData->Map(); + screen->mLights->Map(); + + RenderBSP(level.HeadNode()); + + // And now the crappy hacks that have to be done to avoid rendering anomalies. + // These cannot be multithreaded when the time comes because all these depend + // on the global 'validcount' variable. + + HandleMissingTextures(in_area); // Missing upper/lower textures + HandleHackedSubsectors(); // open sector hacks for deep water + ProcessSectorStacks(in_area); // merge visplanes of sector stacks + PrepareUnhandledMissingTextures(); + screen->mLights->Unmap(); + screen->mVertexData->Unmap(); + + ProcessAll.Unclock(); + +} + +//----------------------------------------------------------------------------- +// +// RenderScene +// +// Draws the current draw lists for the non GLSL renderer +// +//----------------------------------------------------------------------------- + +void HWDrawInfo::RenderScene(FRenderState &state) +{ + const auto &vp = Viewpoint; + RenderAll.Clock(); + + state.SetDepthMask(true); + + screen->mLights->BindBase(); + state.EnableFog(true); + state.SetRenderStyle(STYLE_Source); + + if (gl_sort_textures) + { + drawlists[GLDL_PLAINWALLS].SortWalls(); + drawlists[GLDL_PLAINFLATS].SortFlats(); + drawlists[GLDL_MASKEDWALLS].SortWalls(); + drawlists[GLDL_MASKEDFLATS].SortFlats(); + drawlists[GLDL_MASKEDWALLSOFS].SortWalls(); + } + + // Part 1: solid geometry. This is set up so that there are no transparent parts + state.SetDepthFunc(DF_Less); + state.AlphaFunc(Alpha_GEqual, 0.f); + state.ClearDepthBias(); + + state.EnableTexture(gl_texture); + state.EnableBrightmap(true); + drawlists[GLDL_PLAINWALLS].DrawWalls(this, state, false); + drawlists[GLDL_PLAINFLATS].DrawFlats(this, state, false); + + + // Part 2: masked geometry. This is set up so that only pixels with alpha>gl_mask_threshold will show + state.AlphaFunc(Alpha_GEqual, gl_mask_threshold); + drawlists[GLDL_MASKEDWALLS].DrawWalls(this, state, false); + drawlists[GLDL_MASKEDFLATS].DrawFlats(this, state, false); + + // Part 3: masked geometry with polygon offset. This list is empty most of the time so only waste time on it when in use. + if (drawlists[GLDL_MASKEDWALLSOFS].Size() > 0) + { + state.SetDepthBias(-1, -128); + drawlists[GLDL_MASKEDWALLSOFS].DrawWalls(this, state, false); + state.ClearDepthBias(); + } + + drawlists[GLDL_MODELS].Draw(this, state, false); + + state.SetRenderStyle(STYLE_Translucent); + + // Part 4: Draw decals (not a real pass) + state.SetDepthFunc(DF_LEqual); + DrawDecals(state, Decals[0]); + + RenderAll.Unclock(); +} + +//----------------------------------------------------------------------------- +// +// RenderTranslucent +// +//----------------------------------------------------------------------------- + +void HWDrawInfo::RenderTranslucent(FRenderState &state) +{ + RenderAll.Clock(); + + // final pass: translucent stuff + state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); + state.SetRenderStyle(STYLE_Translucent); + + state.EnableBrightmap(true); + drawlists[GLDL_TRANSLUCENTBORDER].Draw(this, state, true); + state.SetDepthMask(false); + + drawlists[GLDL_TRANSLUCENT].DrawSorted(this, state); + state.EnableBrightmap(false); + + + state.AlphaFunc(Alpha_GEqual, 0.5f); + state.SetDepthMask(true); + + RenderAll.Unclock(); +} + + +//----------------------------------------------------------------------------- +// +// RenderTranslucent +// +//----------------------------------------------------------------------------- + +void HWDrawInfo::RenderPortal(HWPortal *p, FRenderState &state, bool usestencil) +{ + auto gp = static_cast(p); + gp->SetupStencil(this, state, usestencil); + auto new_di = StartDrawInfo(this, Viewpoint, &VPUniforms); + new_di->mCurrentPortal = gp; + state.SetLightIndex(-1); + gp->DrawContents(new_di, state); + new_di->EndDrawInfo(); + state.SetVertexBuffer(screen->mVertexData); + screen->mViewpoints->Bind(state, vpIndex); + gp->RemoveStencil(this, state, usestencil); + +} + +//----------------------------------------------------------------------------- +// +// Draws player sprites and color blend +// +//----------------------------------------------------------------------------- + + +void HWDrawInfo::EndDrawScene(sector_t * viewsector, FRenderState &state) +{ + state.EnableFog(false); + + // [BB] HUD models need to be rendered here. + const bool renderHUDModel = IsHUDModelForPlayerAvailable(players[consoleplayer].camera->player); + if (renderHUDModel) + { + // [BB] The HUD model should be drawn over everything else already drawn. + state.Clear(CT_Depth); + DrawPlayerSprites(true, state); + } + + state.EnableStencil(false); + state.SetViewport(screen->mScreenViewport.left, screen->mScreenViewport.top, screen->mScreenViewport.width, screen->mScreenViewport.height); + + // Restore standard rendering state + state.SetRenderStyle(STYLE_Translucent); + state.ResetColor(); + state.EnableTexture(true); + state.SetScissor(0, 0, -1, -1); +} + +void HWDrawInfo::DrawEndScene2D(sector_t * viewsector, FRenderState &state) +{ + const bool renderHUDModel = IsHUDModelForPlayerAvailable(players[consoleplayer].camera->player); + auto vrmode = VRMode::GetVRMode(true); + + HWViewpointUniforms vp = VPUniforms; + vp.mViewMatrix.loadIdentity(); + vp.mProjectionMatrix = vrmode->GetHUDSpriteProjection(); + screen->mViewpoints->SetViewpoint(state, &vp); + state.EnableDepthTest(false); + state.EnableMultisampling(false); + + DrawPlayerSprites(false, state); + + state.SetSoftLightLevel(-1); + + // Restore standard rendering state + state.SetRenderStyle(STYLE_Translucent); + state.ResetColor(); + state.EnableTexture(true); + state.SetScissor(0, 0, -1, -1); +} + +//----------------------------------------------------------------------------- +// +// sets 3D viewport and initial state +// +//----------------------------------------------------------------------------- + +void HWDrawInfo::Set3DViewport(FRenderState &state) +{ + // Always clear all buffers with scissor test disabled. + // This is faster on newer hardware because it allows the GPU to skip + // reading from slower memory where the full buffers are stored. + state.SetScissor(0, 0, -1, -1); + state.Clear(CT_Color | CT_Depth | CT_Stencil); + + const auto &bounds = screen->mSceneViewport; + state.SetViewport(bounds.left, bounds.top, bounds.width, bounds.height); + state.SetScissor(bounds.left, bounds.top, bounds.width, bounds.height); + state.EnableMultisampling(true); + state.EnableDepthTest(true); + state.EnableStencil(true); + state.SetStencil(0, SOP_Keep, SF_AllOn); +} + +//----------------------------------------------------------------------------- +// +// R_RenderView - renders one view - either the screen or a camera texture +// +//----------------------------------------------------------------------------- + +void HWDrawInfo::ProcessScene(bool toscreen, const std::function &drawScene) +{ + screen->mPortalState->BeginScene(); + + int mapsection = R_PointInSubsector(Viewpoint.Pos)->mapsection; + CurrentMapSections.Set(mapsection); + DrawScene = drawScene; + DrawScene(this, toscreen ? DM_MAINVIEW : DM_OFFSCREEN); + +} diff --git a/src/hwrenderer/scene/hw_drawinfo.h b/src/hwrenderer/scene/hw_drawinfo.h index 7f403e54d..1eb4f6282 100644 --- a/src/hwrenderer/scene/hw_drawinfo.h +++ b/src/hwrenderer/scene/hw_drawinfo.h @@ -1,12 +1,22 @@ #pragma once #include +#include #include "vectors.h" #include "r_defs.h" #include "r_utility.h" #include "hw_viewpointuniforms.h" #include "v_video.h" +#include "hw_weapon.h" +#include "hw_drawlist.h" +enum EDrawMode +{ + DM_MAINVIEW, + DM_OFFSCREEN, + DM_PORTAL, + DM_SKYPORTAL +}; struct FSectorPortalGroup; struct FLinePortalSpan; @@ -20,10 +30,11 @@ struct particle_t; struct FDynLightData; struct HUDSprite; class Clipper; -class IPortal; -class FFlatVertexGenerator; +class HWPortal; +class FFlatVertexBuffer; class IRenderQueue; class HWScenePortalBase; +class FRenderState; //========================================================================== // @@ -34,6 +45,16 @@ struct gl_subsectorrendernode { gl_subsectorrendernode * next; subsector_t * sub; + int lightindex; + int vertexindex; +}; + +struct gl_floodrendernode +{ + gl_floodrendernode * next; + seg_t *seg; + int vertexindex; + // This will use the light list of the originating sector. }; enum area_t : int; @@ -56,6 +77,21 @@ enum EPortalClip PClip_Behind, }; +enum DrawListType +{ + GLDL_PLAINWALLS, + GLDL_PLAINFLATS, + GLDL_MASKEDWALLS, + GLDL_MASKEDFLATS, + GLDL_MASKEDWALLSOFS, + GLDL_MODELS, + + GLDL_TRANSLUCENT, + GLDL_TRANSLUCENTBORDER, + + GLDL_TYPES, +}; + struct HWDrawInfo { @@ -100,17 +136,21 @@ struct HWDrawInfo bool isNightvision() const { return !!(FullbrightFlags & Nightvision); } bool isStealthVision() const { return !!(FullbrightFlags & StealthVision); } + HWDrawList drawlists[GLDL_TYPES]; + int vpIndex; + HWDrawInfo * outer = nullptr; int FullbrightFlags; std::atomic spriteindex; HWScenePortalBase *mClipPortal; - IPortal *mCurrentPortal; + HWPortal *mCurrentPortal; //FRotator mAngles; - IShadowMap *mShadowMap; Clipper *mClipper; FRenderViewpoint Viewpoint; HWViewpointUniforms VPUniforms; // per-viewpoint uniform state - TArray Portals; + TArray Portals; + TArray Decals[2]; // the second slot is for mirrors which get rendered in a separate pass. + TArray hudsprites; // These may just be stored by value. TArray MissingUpperTextures; TArray MissingLowerTextures; @@ -122,6 +162,8 @@ struct HWDrawInfo TArray otherfloorplanes; TArray otherceilingplanes; + TArray floodfloorsegs; + TArray floodceilingsegs; TArray CeilingStacks; TArray FloorStacks; @@ -136,8 +178,9 @@ struct HWDrawInfo BitArray CurrentMapSections; // this cannot be a single number, because a group of portals with the same displacement may link different sections. area_t in_area; fixed_t viewx, viewy; // since the nodes are still fixed point, keeping the view position also fixed point for node traversal is faster. - FFlatVertexGenerator *mVBO; // this class needs access because the sector vertex updating is part of BSP traversal. + bool multithread; + std::function DrawScene = nullptr; private: // For ProcessLowerMiniseg @@ -150,18 +193,51 @@ private: sector_t fakesec; // this is a struct member because it gets used in recursively called functions so it cannot be put on the stack. + void WorkerThread(); void UnclipSubsector(subsector_t *sub); + void AddLine(seg_t *seg, bool portalclip); void PolySubsector(subsector_t * sub); void RenderPolyBSPNode(void *node); void AddPolyobjs(subsector_t *sub); void AddLines(subsector_t * sub, sector_t * sector); void AddSpecialPortalLines(subsector_t * sub, sector_t * sector, line_t *line); + public: void RenderThings(subsector_t * sub, sector_t * sector); + void RenderParticles(subsector_t *sub, sector_t *front); void DoSubsector(subsector_t * sub); + int SetupLightsForOtherPlane(subsector_t * sub, FDynLightData &lightdata, const secplane_t *plane); + int CreateOtherPlaneVertices(subsector_t *sub, const secplane_t *plane); + void DrawPSprite(HUDSprite *huds, FRenderState &state); public: + gl_subsectorrendernode * GetOtherFloorPlanes(unsigned int sector) + { + if (sectorhwcaps & RFL_NO_CLIP_PLANES) && VPUniforms.mClipLine.X > -1000000.f; } - IPortal * FindPortal(const void * src); + HWPortal * FindPortal(const void * src); void RenderBSPNode(void *node); + void RenderBSP(void *node); + static HWDrawInfo *StartDrawInfo(HWDrawInfo *parent, FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms); + void StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms); void ClearBuffers(); + HWDrawInfo *EndDrawInfo(); void SetViewArea(); int SetFullbrightFlags(player_t *player); + void CreateScene(); + void RenderScene(FRenderState &state); + void RenderTranslucent(FRenderState &state); + void RenderPortal(HWPortal *p, FRenderState &state, bool usestencil); + void EndDrawScene(sector_t * viewsector, FRenderState &state); + void DrawEndScene2D(sector_t * viewsector, FRenderState &state); + void Set3DViewport(FRenderState &state); + void ProcessScene(bool toscreen, const std::function &drawScene); + bool DoOneSectorUpper(subsector_t * subsec, float planez, area_t in_area); bool DoOneSectorLower(subsector_t * subsec, float planez, area_t in_area); bool DoFakeBridge(subsector_t * subsec, float planez, area_t in_area); @@ -207,7 +296,12 @@ public: void AddUpperMissingTexture(side_t * side, subsector_t *sub, float backheight); void AddLowerMissingTexture(side_t * side, subsector_t *sub, float backheight); void HandleMissingTextures(area_t in_area); - void DrawUnhandledMissingTextures(); + void PrepareUnhandledMissingTextures(); + void PrepareUpperGap(seg_t * seg); + void PrepareLowerGap(seg_t * seg); + void CreateFloodPoly(wallseg * ws, FFlatVertex *vertices, float planez, sector_t * sec, bool ceiling); + void CreateFloodStencilPoly(wallseg * ws, FFlatVertex *vertices); + void AddHackedSubsector(subsector_t * sub); void HandleHackedSubsectors(); void AddFloorStack(sector_t * sec); @@ -227,30 +321,21 @@ public: void UpdateCurrentMapSection(); void SetViewMatrix(const FRotator &angles, float vx, float vy, float vz, bool mirror, bool planemirror); - void SetupView(float vx, float vy, float vz, bool mirror, bool planemirror); + void SetupView(FRenderState &state, float vx, float vy, float vz, bool mirror, bool planemirror); angle_t FrustumAngle(); - virtual void DrawWall(GLWall *wall, int pass) = 0; - virtual void DrawFlat(GLFlat *flat, int pass, bool trans) = 0; - virtual void DrawSprite(GLSprite *sprite, int pass) = 0; + void DrawDecals(FRenderState &state, TArray &decals); + void DrawPlayerSprites(bool hudModelStep, FRenderState &state); - virtual void FloodUpperGap(seg_t * seg) = 0; - virtual void FloodLowerGap(seg_t * seg) = 0; void ProcessLowerMinisegs(TArray &lowersegs); - virtual void AddSubsectorToPortal(FSectorPortalGroup *portal, subsector_t *sub) = 0; + void AddSubsectorToPortal(FSectorPortalGroup *portal, subsector_t *sub); - virtual void AddWall(GLWall *w) = 0; - virtual void AddPortal(GLWall *w, int portaltype) = 0; - virtual void AddMirrorSurface(GLWall *w) = 0; - virtual void AddFlat(GLFlat *flat, bool fog) = 0; - virtual void AddSprite(GLSprite *sprite, bool translucent) = 0; - virtual void AddHUDSprite(HUDSprite *huds) = 0; + void AddWall(GLWall *w); + void AddMirrorSurface(GLWall *w); + void AddFlat(GLFlat *flat, bool fog); + void AddSprite(GLSprite *sprite, bool translucent); - virtual int UploadLights(FDynLightData &data) = 0; - virtual void ApplyVPUniforms() = 0; - virtual bool SetDepthClamp(bool on) = 0; - virtual GLDecal *AddDecal(bool onmirror) = 0; - virtual std::pair AllocVertices(unsigned int count) = 0; + GLDecal *AddDecal(bool onmirror); }; diff --git a/src/hwrenderer/scene/hw_drawlist.cpp b/src/hwrenderer/scene/hw_drawlist.cpp index c1677646b..1ccbc31f3 100644 --- a/src/hwrenderer/scene/hw_drawlist.cpp +++ b/src/hwrenderer/scene/hw_drawlist.cpp @@ -32,7 +32,10 @@ #include "g_levellocals.h" #include "hwrenderer/scene/hw_drawstructs.h" #include "hwrenderer/scene/hw_drawlist.h" +#include "hwrenderer/data/flatvertices.h" #include "hwrenderer/utility/hw_clock.h" +#include "hw_renderstate.h" +#include "hw_drawinfo.h" FMemArena RenderDataAllocator(1024*1024); // Use large blocks to reduce allocation time. @@ -771,7 +774,7 @@ GLSprite *HWDrawList::NewSprite() // // //========================================================================== -void HWDrawList::DoDraw(HWDrawInfo *di, int pass, int i, bool trans) +void HWDrawList::DoDraw(HWDrawInfo *di, FRenderState &state, bool translucent, int i) { switch(drawitems[i].rendertype) { @@ -779,7 +782,7 @@ void HWDrawList::DoDraw(HWDrawInfo *di, int pass, int i, bool trans) { GLFlat * f= flats[drawitems[i].index]; RenderFlat.Clock(); - di->DrawFlat(f, pass, trans); + f->DrawFlat(di, state, translucent); RenderFlat.Unclock(); } break; @@ -788,7 +791,7 @@ void HWDrawList::DoDraw(HWDrawInfo *di, int pass, int i, bool trans) { GLWall * w= walls[drawitems[i].index]; RenderWall.Clock(); - di->DrawWall(w, pass); + w->DrawWall(di, state, translucent); RenderWall.Unclock(); } break; @@ -797,7 +800,7 @@ void HWDrawList::DoDraw(HWDrawInfo *di, int pass, int i, bool trans) { GLSprite * s= sprites[drawitems[i].index]; RenderSprite.Clock(); - di->DrawSprite(s, pass); + s->DrawSprite(di, state, translucent); RenderSprite.Unclock(); } break; @@ -809,11 +812,11 @@ void HWDrawList::DoDraw(HWDrawInfo *di, int pass, int i, bool trans) // // //========================================================================== -void HWDrawList::Draw(HWDrawInfo *di, int pass, bool trans) +void HWDrawList::Draw(HWDrawInfo *di, FRenderState &state, bool translucent) { for (unsigned i = 0; i < drawitems.Size(); i++) { - DoDraw(di, pass, i, trans); + DoDraw(di, state, translucent, i); } } @@ -822,12 +825,12 @@ void HWDrawList::Draw(HWDrawInfo *di, int pass, bool trans) // // //========================================================================== -void HWDrawList::DrawWalls(HWDrawInfo *di, int pass) +void HWDrawList::DrawWalls(HWDrawInfo *di, FRenderState &state, bool translucent) { RenderWall.Clock(); for (auto &item : drawitems) { - di->DrawWall(walls[item.index], pass); + walls[item.index]->DrawWall(di, state, translucent); } RenderWall.Unclock(); } @@ -837,13 +840,97 @@ void HWDrawList::DrawWalls(HWDrawInfo *di, int pass) // // //========================================================================== -void HWDrawList::DrawFlats(HWDrawInfo *di, int pass) +void HWDrawList::DrawFlats(HWDrawInfo *di, FRenderState &state, bool translucent) { RenderFlat.Clock(); for (unsigned i = 0; iDrawFlat(flats[drawitems[i].index], pass, false); + flats[drawitems[i].index]->DrawFlat(di, state, translucent); } RenderFlat.Unclock(); } +//========================================================================== +// +// +// +//========================================================================== +void HWDrawList::DrawSorted(HWDrawInfo *di, FRenderState &state, SortNode * head) +{ + float clipsplit[2]; + int relation = 0; + float z = 0.f; + + state.GetClipSplit(clipsplit); + + if (drawitems[head->itemindex].rendertype == GLDIT_FLAT) + { + z = flats[drawitems[head->itemindex].index]->z; + relation = z > di->Viewpoint.Pos.Z ? 1 : -1; + } + + + // left is further away, i.e. for stuff above viewz its z coordinate higher, for stuff below viewz its z coordinate is lower + if (head->left) + { + if (relation == -1) + { + state.SetClipSplit(clipsplit[0], z); // render below: set flat as top clip plane + } + else if (relation == 1) + { + state.SetClipSplit(z, clipsplit[1]); // render above: set flat as bottom clip plane + } + DrawSorted(di, state, head->left); + state.SetClipSplit(clipsplit); + } + DoDraw(di, state, true, head->itemindex); + if (head->equal) + { + SortNode * ehead = head->equal; + while (ehead) + { + DoDraw(di, state, true, ehead->itemindex); + ehead = ehead->equal; + } + } + // right is closer, i.e. for stuff above viewz its z coordinate is lower, for stuff below viewz its z coordinate is higher + if (head->right) + { + if (relation == 1) + { + state.SetClipSplit(clipsplit[0], z); // render below: set flat as top clip plane + } + else if (relation == -1) + { + state.SetClipSplit(z, clipsplit[1]); // render above: set flat as bottom clip plane + } + DrawSorted(di, state, head->right); + state.SetClipSplit(clipsplit); + } +} + +//========================================================================== +// +// +// +//========================================================================== +void HWDrawList::DrawSorted(HWDrawInfo *di, FRenderState &state) +{ + if (drawitems.Size() == 0) return; + + if (!sorted) + { + screen->mVertexData->Map(); + Sort(di); + screen->mVertexData->Unmap(); + } + state.ClearClipSplit(); + state.EnableClipDistance(1, true); + state.EnableClipDistance(2, true); + DrawSorted(di, state, sorted); + state.EnableClipDistance(1, false); + state.EnableClipDistance(2, false); + state.ClearClipSplit(); +} + diff --git a/src/hwrenderer/scene/hw_drawlist.h b/src/hwrenderer/scene/hw_drawlist.h index 22bf1cb31..303da06fb 100644 --- a/src/hwrenderer/scene/hw_drawlist.h +++ b/src/hwrenderer/scene/hw_drawlist.h @@ -1,10 +1,14 @@ #pragma once -#include "hwrenderer/scene/hw_drawinfo.h" #include "memarena.h" extern FMemArena RenderDataAllocator; void ResetRenderDataAllocator(); +struct HWDrawInfo; +class GLWall; +class GLFlat; +class GLSprite; +class FRenderState; //========================================================================== // @@ -103,11 +107,14 @@ public: SortNode * DoSort(HWDrawInfo *di, SortNode * head); void Sort(HWDrawInfo *di); - void DoDraw(HWDrawInfo *di, int pass, int index, bool trans); - void Draw(HWDrawInfo *di, int pass, bool trans = false); - void DrawWalls(HWDrawInfo *di, int pass); - void DrawFlats(HWDrawInfo *di, int pass); - + void DoDraw(HWDrawInfo *di, FRenderState &state, bool translucent, int i); + void Draw(HWDrawInfo *di, FRenderState &state, bool translucent); + void DrawWalls(HWDrawInfo *di, FRenderState &state, bool translucent); + void DrawFlats(HWDrawInfo *di, FRenderState &state, bool translucent); + + void DrawSorted(HWDrawInfo *di, FRenderState &state, SortNode * head); + void DrawSorted(HWDrawInfo *di, FRenderState &state); + HWDrawList * next; } ; diff --git a/src/hwrenderer/scene/hw_drawlistadd.cpp b/src/hwrenderer/scene/hw_drawlistadd.cpp new file mode 100644 index 000000000..a1657b396 --- /dev/null +++ b/src/hwrenderer/scene/hw_drawlistadd.cpp @@ -0,0 +1,150 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2000-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/utility/hw_cvars.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hwrenderer/scene/hw_drawstructs.h" +#include "hwrenderer/scene/hw_drawinfo.h" +#include "hwrenderer/textures/hw_material.h" + +EXTERN_CVAR(Bool, gl_seamless) + +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::AddWall(GLWall *wall) +{ + if (wall->flags & GLWall::GLWF_TRANSLUCENT) + { + auto newwall = drawlists[GLDL_TRANSLUCENT].NewWall(); + *newwall = *wall; + } + else + { + bool masked = GLWall::passflag[wall->type] == 1 ? false : (wall->gltexture && wall->gltexture->isMasked()); + int list; + + if ((wall->flags & GLWall::GLWF_SKYHACK && wall->type == RENDERWALL_M2S)) + { + list = GLDL_MASKEDWALLSOFS; + } + else + { + list = masked ? GLDL_MASKEDWALLS : GLDL_PLAINWALLS; + } + auto newwall = drawlists[list].NewWall(); + *newwall = *wall; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::AddMirrorSurface(GLWall *w) +{ + w->type = RENDERWALL_MIRRORSURFACE; + auto newwall = drawlists[GLDL_TRANSLUCENTBORDER].NewWall(); + *newwall = *w; + + // Invalidate vertices to allow setting of texture coordinates + newwall->vertcount = 0; + + FVector3 v = newwall->glseg.Normal(); + auto tcs = newwall->tcs; + tcs[GLWall::LOLFT].u = tcs[GLWall::LORGT].u = tcs[GLWall::UPLFT].u = tcs[GLWall::UPRGT].u = v.X; + tcs[GLWall::LOLFT].v = tcs[GLWall::LORGT].v = tcs[GLWall::UPLFT].v = tcs[GLWall::UPRGT].v = v.Z; + newwall->MakeVertices(this, false); + newwall->ProcessDecals(this); +} + +//========================================================================== +// +// FDrawInfo::AddFlat +// +// Checks texture, lighting and translucency settings and puts this +// plane in the appropriate render list. +// +//========================================================================== + +void HWDrawInfo::AddFlat(GLFlat *flat, bool fog) +{ + int list; + + if (flat->renderstyle != STYLE_Translucent || flat->alpha < 1.f - FLT_EPSILON || fog || flat->gltexture == nullptr) + { + // translucent 3D floors go into the regular translucent list, translucent portals go into the translucent border list. + list = (flat->renderflags&SSRF_RENDER3DPLANES) ? GLDL_TRANSLUCENT : GLDL_TRANSLUCENTBORDER; + } + else if (flat->gltexture->tex->GetTranslucency()) + { + if (flat->stack) + { + list = GLDL_TRANSLUCENTBORDER; + } + else if ((flat->renderflags&SSRF_RENDER3DPLANES) && !flat->plane.plane.isSlope()) + { + list = GLDL_TRANSLUCENT; + } + else + { + list = GLDL_PLAINFLATS; + } + } + else + { + bool masked = flat->gltexture->isMasked() && ((flat->renderflags&SSRF_RENDER3DPLANES) || flat->stack); + list = masked ? GLDL_MASKEDFLATS : GLDL_PLAINFLATS; + } + auto newflat = drawlists[list].NewFlat(); + *newflat = *flat; +} + + +//========================================================================== +// +// +// +//========================================================================== +void HWDrawInfo::AddSprite(GLSprite *sprite, bool translucent) +{ + int list; + // [BB] Allow models to be drawn in the GLDL_TRANSLUCENT pass. + if (translucent || sprite->actor == nullptr || (!sprite->modelframe && (sprite->actor->renderflags & RF_SPRITETYPEMASK) != RF_WALLSPRITE)) + { + list = GLDL_TRANSLUCENT; + } + else + { + list = GLDL_MODELS; + } + + auto newsprt = drawlists[list].NewSprite(); + *newsprt = *sprite; +} + diff --git a/src/hwrenderer/scene/hw_drawstructs.h b/src/hwrenderer/scene/hw_drawstructs.h index cdeb410ce..38514a6bf 100644 --- a/src/hwrenderer/scene/hw_drawstructs.h +++ b/src/hwrenderer/scene/hw_drawstructs.h @@ -25,6 +25,8 @@ struct FDynLightData; class VSMatrix; struct FSpriteModelFrame; struct particle_t; +class FRenderState; +struct GLDecal; enum area_t : int; enum HWRenderStyle @@ -145,7 +147,7 @@ public: }; friend struct HWDrawList; - friend class GLPortal; + friend class HWPortal; vertex_t * vertexes[2]; // required for polygon splitting FMaterial *gltexture; @@ -261,20 +263,14 @@ public: int CountVertices(); + void RenderWall(HWDrawInfo *di, FRenderState &state, int textured); + void RenderFogBoundary(HWDrawInfo *di, FRenderState &state); + void RenderMirrorSurface(HWDrawInfo *di, FRenderState &state); + void RenderTexturedWall(HWDrawInfo *di, FRenderState &state, int rflags); + void RenderTranslucentWall(HWDrawInfo *di, FRenderState &state); + void DrawDecalsForMirror(HWDrawInfo *di, FRenderState &state, TArray &decals); + public: - GLWall() {} - - GLWall(const GLWall &other) - { - memcpy(this, &other, sizeof(GLWall)); - } - - GLWall & operator=(const GLWall &other) - { - memcpy(this, &other, sizeof(GLWall)); - return *this; - } - void Process(HWDrawInfo *di, seg_t *seg, sector_t *frontsector, sector_t *backsector); void ProcessLowerMiniseg(HWDrawInfo *di, seg_t *seg, sector_t *frontsector, sector_t *backsector); @@ -283,6 +279,8 @@ public: return -((y-glseg.y1)*(glseg.x2-glseg.x1)-(x-glseg.x1)*(glseg.y2-glseg.y1)); } + void DrawWall(HWDrawInfo *di, FRenderState &state, bool translucent); + }; //========================================================================== @@ -295,7 +293,6 @@ class GLFlat { public: sector_t * sector; - float dz; // z offset for rendering hacks float z; // the z position of the flat (only valid for non-sloped planes) FMaterial *gltexture; @@ -315,28 +312,15 @@ public: int dynlightindex; void CreateSkyboxVertices(FFlatVertex *buffer); - bool SetupLights(int pass, FLightNode *head, FDynLightData &lightdata, int portalgroup); - bool SetupSubsectorLights(int pass, subsector_t * sub, FDynLightData &lightdata); - bool SetupSectorLights(int pass, sector_t * sec, FDynLightData &lightdata); + void SetupLights(HWDrawInfo *di, FLightNode *head, FDynLightData &lightdata, int portalgroup); void PutFlat(HWDrawInfo *di, bool fog = false); void Process(HWDrawInfo *di, sector_t * model, int whichplane, bool notexture); void SetFrom3DFloor(F3DFloor *rover, bool top, bool underside); void ProcessSector(HWDrawInfo *di, sector_t * frontsector); - GLFlat() {} - - GLFlat(const GLFlat &other) - { - memcpy(this, &other, sizeof(GLFlat)); - } - - GLFlat & operator=(const GLFlat &other) - { - memcpy(this, &other, sizeof(GLFlat)); - return *this; - } - + void DrawSubsectors(HWDrawInfo *di, FRenderState &state); + void DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent); }; //========================================================================== @@ -353,6 +337,7 @@ public: uint8_t foglevel; uint8_t hw_styleflags; bool fullbright; + bool polyoffset; PalEntry ThingColor; // thing's own color FColormap Colormap; FSpriteModelFrame * modelframe; @@ -362,6 +347,7 @@ public: int translation; int index; int depth; + int vertexindex; float topclip; float bottomclip; @@ -388,22 +374,12 @@ public: public: - GLSprite() {} + void CreateVertices(HWDrawInfo *di); void PutSprite(HWDrawInfo *di, bool translucent); void Process(HWDrawInfo *di, AActor* thing,sector_t * sector, area_t in_area, int thruportal = false); void ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *sector);//, int shade, int fakeside) - GLSprite(const GLSprite &other) - { - memcpy(this, &other, sizeof(GLSprite)); - } - - GLSprite & operator=(const GLSprite &other) - { - memcpy(this, &other, sizeof(GLSprite)); - return *this; - } - + void DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent); }; @@ -432,6 +408,8 @@ struct GLDecal secplane_t bottomplane; FVector3 Normal; + void DrawDecal(HWDrawInfo *di, FRenderState &state); + }; diff --git a/src/hwrenderer/scene/hw_flats.cpp b/src/hwrenderer/scene/hw_flats.cpp index 0c3ec07e4..776346114 100644 --- a/src/hwrenderer/scene/hw_flats.cpp +++ b/src/hwrenderer/scene/hw_flats.cpp @@ -42,7 +42,9 @@ #include "hwrenderer/textures/hw_material.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" #include "hw_drawstructs.h" +#include "hw_renderstate.h" #ifdef _DEBUG CVAR(Int, gl_breaksec, -1, 0) @@ -118,7 +120,6 @@ void GLFlat::CreateSkyboxVertices(FFlatVertex *vert) if (y > maxy) maxy = y; } - float z = plane.plane.ZatPoint(0., 0.) + dz; static float uvals[] = { 0, 0, 1, 1 }; static float vvals[] = { 1, 0, 0, 1 }; int rot = -xs_FloorToInt(plane.Angle / 90.f); @@ -135,13 +136,16 @@ void GLFlat::CreateSkyboxVertices(FFlatVertex *vert) // //========================================================================== -bool GLFlat::SetupLights(int pass, FLightNode * node, FDynLightData &lightdata, int portalgroup) +void GLFlat::SetupLights(HWDrawInfo *di, FLightNode * node, FDynLightData &lightdata, int portalgroup) { Plane p; lightdata.Clear(); - if (renderstyle == STYLE_Add && !level.lightadditivesurfaces) return false; // no lights on additively blended surfaces. - + if (renderstyle == STYLE_Add && !level.lightadditivesurfaces) + { + dynlightindex = -1; + return; // no lights on additively blended surfaces. + } while (node) { ADynamicLight * light = node->lightsource; @@ -163,21 +167,183 @@ bool GLFlat::SetupLights(int pass, FLightNode * node, FDynLightData &lightdata, } p.Set(plane.plane.Normal(), plane.plane.fD()); - lightdata.GetLight(portalgroup, p, light, false); + draw_dlightf += lightdata.GetLight(portalgroup, p, light, false); node = node->nextLight; } - return true; + dynlightindex = screen->mLights->UploadLights(lightdata); } -bool GLFlat::SetupSubsectorLights(int pass, subsector_t * sub, FDynLightData &lightdata) +//========================================================================== +// +// +// +//========================================================================== + +void GLFlat::DrawSubsectors(HWDrawInfo *di, FRenderState &state) { - return SetupLights(pass, sub->lighthead, lightdata, sub->sector->PortalGroup); + auto vcount = sector->ibocount; + + if (level.HasDynamicLights && screen->BuffersArePersistent()) + { + SetupLights(di, sector->lighthead, lightdata, sector->PortalGroup); + } + state.SetLightIndex(dynlightindex); + if (vcount > 0 && !di->ClipLineShouldBeActive()) + { + state.DrawIndexed(DT_Triangles, iboindex, vcount); + flatvertices += vcount; + flatprimitives++; + } + else + { + int index = iboindex; + bool applied = false; + for (int i = 0; i < sector->subsectorcount; i++) + { + subsector_t * sub = sector->subsectors[i]; + if (sub->numlines <= 2) continue; + + if (di->ss_renderflags[sub->Index()] & renderflags) + { + state.DrawIndexed(DT_Triangles, index, (sub->numlines - 2) * 3, !applied); + applied = true; + flatvertices += sub->numlines; + flatprimitives++; + } + index += (sub->numlines - 2) * 3; + } + } + + if (!(renderflags&SSRF_RENDER3DPLANES)) + { + // Draw the subsectors assigned to it due to missing textures + gl_subsectorrendernode * node = (renderflags&SSRF_RENDERFLOOR) ? + di->GetOtherFloorPlanes(sector->sectornum) : + di->GetOtherCeilingPlanes(sector->sectornum); + + while (node) + { + state.SetLightIndex(node->lightindex); + auto num = node->sub->numlines; + flatvertices += num; + flatprimitives++; + state.Draw(DT_TriangleFan,node->vertexindex, num); + node = node->next; + } + // Flood gaps with the back side's ceiling/floor texture + // This requires a stencil because the projected plane interferes with + // the depth buffer + gl_floodrendernode * fnode = (renderflags&SSRF_RENDERFLOOR) ? + di->GetFloodFloorSegs(sector->sectornum) : + di->GetFloodCeilingSegs(sector->sectornum); + + state.SetLightIndex(dynlightindex); + while (fnode) + { + flatvertices += 12; + flatprimitives += 3; + + // Push bleeding floor/ceiling textures back a little in the z-buffer + // so they don't interfere with overlapping mid textures. + state.SetDepthBias(1, 128); + + // Create stencil + state.SetEffect(EFF_STENCIL); + state.EnableTexture(false); + state.SetStencil(0, SOP_Increment, SF_ColorMaskOff); + state.Draw(DT_TriangleFan,fnode->vertexindex, 4); + + // Draw projected plane into stencil + state.EnableTexture(true); + state.SetEffect(EFF_NONE); + state.SetStencil(1, SOP_Keep, SF_DepthMaskOff); + state.EnableDepthTest(false); + state.Draw(DT_TriangleFan,fnode->vertexindex + 4, 4); + + // clear stencil + state.SetEffect(EFF_STENCIL); + state.EnableTexture(false); + state.SetStencil(1, SOP_Decrement, SF_ColorMaskOff | SF_DepthMaskOff); + state.Draw(DT_TriangleFan,fnode->vertexindex, 4); + + // restore old stencil op. + state.EnableTexture(true); + state.EnableDepthTest(true); + state.SetEffect(EFF_NONE); + state.SetDepthBias(0, 0); + state.SetStencil(0, SOP_Keep, SF_AllOn); + + fnode = fnode->next; + } + + } } -bool GLFlat::SetupSectorLights(int pass, sector_t * sec, FDynLightData &lightdata) +//========================================================================== +// +// +// +//========================================================================== +void GLFlat::DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent) { - return SetupLights(pass, sec->lighthead, lightdata, sec->PortalGroup); +#ifdef _DEBUG + if (sector->sectornum == gl_breaksec) + { + int a = 0; + } +#endif + + int rel = getExtraLight(); + + state.SetNormal(plane.plane.Normal().X, plane.plane.Normal().Z, plane.plane.Normal().Y); + + state.SetColor(lightlevel, rel, di->isFullbrightScene(), Colormap, alpha); + state.SetFog(lightlevel, rel, di->isFullbrightScene(), &Colormap, false); + if (!gltexture || !gltexture->tex->isFullbright()) + state.SetObjectColor(FlatColor | 0xff000000); + + if (!translucent) + { + if (sector->special != GLSector_Skybox) + { + state.SetMaterial(gltexture, CLAMP_NONE, 0, -1); + state.SetPlaneTextureRotation(&plane, gltexture); + DrawSubsectors(di, state); + state.EnableTextureMatrix(false); + } + else + { + state.SetMaterial(gltexture, CLAMP_XY, 0, -1); + state.SetLightIndex(dynlightindex); + state.Draw(DT_TriangleFan,iboindex, 4); + flatvertices += 4; + flatprimitives++; + } + state.SetObjectColor(0xffffffff); + } + else + { + state.SetRenderStyle(renderstyle); + if (!gltexture) + { + state.AlphaFunc(Alpha_GEqual, 0.f); + state.EnableTexture(false); + DrawSubsectors(di, state); + state.EnableTexture(true); + } + else + { + if (!gltexture->tex->GetTranslucency()) state.AlphaFunc(Alpha_GEqual, gl_mask_threshold); + else state.AlphaFunc(Alpha_GEqual, 0.f); + state.SetMaterial(gltexture, CLAMP_NONE, 0, -1); + state.SetPlaneTextureRotation(&plane, gltexture); + DrawSubsectors(di, state); + state.EnableTextureMatrix(false); + } + state.SetRenderStyle(DefaultRenderStyle()); + state.SetObjectColor(0xffffffff); + } } //========================================================================== @@ -194,7 +360,13 @@ inline void GLFlat::PutFlat(HWDrawInfo *di, bool fog) { Colormap.Clear(); } - dynlightindex = -1; // make sure this is always initialized to something proper. + else if (!screen->BuffersArePersistent()) + { + if (level.HasDynamicLights && gltexture != nullptr) + { + SetupLights(di, sector->lighthead, lightdata, sector->PortalGroup); + } + } di->AddFlat(this, fog); } @@ -232,11 +404,14 @@ void GLFlat::Process(HWDrawInfo *di, sector_t * model, int whichplane, bool fog) lightlevel = abs(lightlevel); } - // get height from vplane - if (whichplane == sector_t::floor && sector->transdoor) dz = -1; - else dz = 0; - z = plane.plane.ZatPoint(0.f, 0.f); + if (sector->special == GLSector_Skybox) + { + auto vert = screen->mVertexData->AllocVertices(4); + CreateSkyboxVertices(vert.first); + iboindex = vert.second; + } + PutFlat(di, fog); rendered_flats++; @@ -270,14 +445,7 @@ void GLFlat::SetFrom3DFloor(F3DFloor *rover, bool top, bool underside) alpha = rover->alpha/255.0f; renderstyle = rover->flags&FF_ADDITIVETRANS? STYLE_Add : STYLE_Translucent; - if (plane.model->VBOHeightcheck(plane.isceiling)) - { - iboindex = plane.vindex; - } - else - { - iboindex = -1; - } + iboindex = plane.vindex; } //========================================================================== @@ -340,14 +508,7 @@ void GLFlat::ProcessSector(HWDrawInfo *di, sector_t * frontsector) if (alpha != 0.f && frontsector->GetTexture(sector_t::floor) != skyflatnum) { - if (frontsector->VBOHeightcheck(sector_t::floor)) - { - iboindex = frontsector->iboindex[sector_t::floor]; - } - else - { - iboindex = -1; - } + iboindex = frontsector->iboindex[sector_t::floor]; ceiling = false; renderflags = SSRF_RENDERFLOOR; @@ -400,15 +561,7 @@ void GLFlat::ProcessSector(HWDrawInfo *di, sector_t * frontsector) if (alpha != 0.f && frontsector->GetTexture(sector_t::ceiling) != skyflatnum) { - if (frontsector->VBOHeightcheck(sector_t::ceiling)) - { - iboindex = frontsector->iboindex[sector_t::ceiling]; - } - else - { - iboindex = -1; - } - + iboindex = frontsector->iboindex[sector_t::ceiling]; ceiling = true; renderflags = SSRF_RENDERCEILING; diff --git a/src/hwrenderer/scene/hw_portal.cpp b/src/hwrenderer/scene/hw_portal.cpp index 84cca96d7..6426e4213 100644 --- a/src/hwrenderer/scene/hw_portal.cpp +++ b/src/hwrenderer/scene/hw_portal.cpp @@ -30,10 +30,16 @@ #include "p_maputl.h" #include "hw_portal.h" #include "hw_clipper.h" -#include "actor.h" +#include "d_player.h" +#include "r_sky.h" #include "g_levellocals.h" +#include "hw_renderstate.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/utility/hw_lighting.h" EXTERN_CVAR(Int, r_mirror_recursions) +EXTERN_CVAR(Bool, gl_portals) //----------------------------------------------------------------------------- // @@ -72,9 +78,9 @@ static FString indent; // //----------------------------------------------------------------------------- -void FPortalSceneState::EndFrame(HWDrawInfo *di) +void FPortalSceneState::EndFrame(HWDrawInfo *di, FRenderState &state) { - IPortal * p; + HWPortal * p; if (gl_portalinfo) { @@ -82,20 +88,15 @@ void FPortalSceneState::EndFrame(HWDrawInfo *di) indent += " "; } - // Only use occlusion query if there are more than 2 portals. - // Otherwise there's too much overhead. - // (And don't forget to consider the separating null pointers!) - bool usequery = di->Portals.Size() > 2 + (unsigned)renderdepth; - while (di->Portals.Pop(p) && p) { if (gl_portalinfo) { - Printf("%sProcessing %s, depth = %d, query = %d\n", indent.GetChars(), p->GetName(), renderdepth, usequery); + Printf("%sProcessing %s, depth = %d\n", indent.GetChars(), p->GetName(), renderdepth); } if (p->lines.Size() > 0) { - p->RenderPortal(true, usequery, di); + RenderPortal(p, state, true, di); } delete p; } @@ -117,10 +118,10 @@ void FPortalSceneState::EndFrame(HWDrawInfo *di) // the GPU and there's rarely more than one sky visible at a time. // //----------------------------------------------------------------------------- -bool FPortalSceneState::RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di) +bool FPortalSceneState::RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di, FRenderState &state) { - IPortal * p; - IPortal * best = nullptr; + HWPortal * p; + HWPortal * best = nullptr; unsigned bestindex = 0; // Find the one with the highest amount of lines. @@ -146,13 +147,174 @@ bool FPortalSceneState::RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di if (best) { portals.Delete(bestindex); - best->RenderPortal(false, false, outer_di); + RenderPortal(best, state, false, outer_di); delete best; return true; } return false; } + +void FPortalSceneState::RenderPortal(HWPortal *p, FRenderState &state, bool usestencil, HWDrawInfo *outer_di) +{ + if (gl_portals) outer_di->RenderPortal(p, state, usestencil); +} + + +//----------------------------------------------------------------------------- +// +// DrawPortalStencil +// +//----------------------------------------------------------------------------- + +void HWPortal::DrawPortalStencil(FRenderState &state, int pass) +{ + if (mPrimIndices.Size() == 0) + { + mPrimIndices.Resize(2 * lines.Size()); + + for (unsigned int i = 0; i < lines.Size(); i++) + { + mPrimIndices[i * 2] = lines[i].vertindex; + mPrimIndices[i * 2 + 1] = lines[i].vertcount; + } + } + for (unsigned int i = 0; i < mPrimIndices.Size(); i += 2) + { + state.Draw(DT_TriangleFan, mPrimIndices[i], mPrimIndices[i + 1], i == 0); + } + if (NeedCap() && lines.Size() > 1) + { + // The cap's depth handling needs special treatment so that it won't block further portal caps. + if (pass == STP_DepthRestore) state.SetDepthRange(1, 1); + state.Draw(DT_TriangleFan, FFlatVertexBuffer::STENCILTOP_INDEX, 4); + state.Draw(DT_TriangleFan, FFlatVertexBuffer::STENCILBOTTOM_INDEX, 4); + if (pass == STP_DepthRestore) state.SetDepthRange(0, 1); + } +} + +//----------------------------------------------------------------------------- +// +// Start +// +//----------------------------------------------------------------------------- + +void HWPortal::SetupStencil(HWDrawInfo *di, FRenderState &state, bool usestencil) +{ + Clocker c(PortalAll); + + rendered_portals++; + if (usestencil) + { + // Create stencil + state.SetEffect(EFF_STENCIL); + state.EnableTexture(false); + state.ResetColor(); + + if (NeedDepthBuffer()) + { + state.SetStencil(0, SOP_Increment, SF_ColorMaskOff | SF_DepthMaskOff); + state.SetDepthFunc(DF_Less); + DrawPortalStencil(state, STP_Stencil); + + // Clear Z-buffer + state.SetStencil(1, SOP_Keep, SF_ColorMaskOff); + state.SetDepthRange(1, 1); + state.SetDepthFunc(DF_Always); + DrawPortalStencil(state, STP_DepthClear); + + // set normal drawing mode + state.EnableTexture(true); + state.SetStencil(1, SOP_Keep, SF_AllOn); + state.SetDepthRange(0, 1); + state.SetDepthFunc(DF_Less); + state.SetEffect(EFF_NONE); + } + else + { + // No z-buffer is needed therefore we can skip all the complicated stuff that is involved + // Note: We must draw the stencil with z-write enabled here because there is no second pass! + state.SetStencil(0, SOP_Increment, SF_ColorMaskOff); + state.SetDepthFunc(DF_Less); + DrawPortalStencil(state, STP_AllInOne); + + state.SetStencil(1, SOP_Keep, SF_DepthMaskOff); + state.EnableDepthTest(false); + state.EnableTexture(true); + state.SetEffect(EFF_NONE); + } + + screen->stencilValue++; + } + else + { + if (!NeedDepthBuffer()) + { + state.SetStencil(0, SOP_Keep, SF_DepthMaskOff); + state.EnableDepthTest(false); + } + } + + // save viewpoint + savedvisibility = di->Viewpoint.camera ? di->Viewpoint.camera->renderflags & RF_MAYBEINVISIBLE : ActorRenderFlags::FromInt(0); +} + +void HWPortal::RemoveStencil(HWDrawInfo *di, FRenderState &state, bool usestencil) +{ + Clocker c(PortalAll); + bool needdepth = NeedDepthBuffer(); + + // Restore the old view + auto &vp = di->Viewpoint; + if (vp.camera != nullptr) vp.camera->renderflags = (vp.camera->renderflags & ~RF_MAYBEINVISIBLE) | savedvisibility; + + state.EnableDepthTest(true); + if (usestencil) + { + state.SetEffect(EFF_NONE); + state.ResetColor(); + state.EnableTexture(false); + + if (needdepth) + { + // first step: reset the depth buffer to max. depth + state.SetStencil(0, SOP_Keep, SF_ColorMaskOff); + state.SetDepthRange(1, 1); // always + state.SetDepthFunc(DF_Always); // write the farthest depth value + DrawPortalStencil(state, STP_DepthClear); + } + + // second step: restore the depth buffer to the previous values and reset the stencil + state.SetStencil(0, SOP_Decrement, SF_ColorMaskOff); + state.SetDepthRange(0, 1); + state.SetDepthFunc(DF_LEqual); + DrawPortalStencil(state, STP_DepthRestore); + + state.EnableTexture(true); + state.SetEffect(EFF_NONE); + screen->stencilValue--; + } + else + { + state.ResetColor(); + state.SetEffect(EFF_STENCIL); + state.EnableTexture(false); + state.SetRenderStyle(STYLE_Source); + + state.SetStencil(0, SOP_Keep, SF_ColorMaskOff | SF_ColorMaskAlpha); // SSAO needs the alpha channel as a marker. + if (needdepth) state.Clear(CT_Depth); + state.SetDepthRange(0, 1); + state.SetDepthFunc(DF_LEqual); + DrawPortalStencil(state, STP_DepthRestore); + + state.SetEffect(EFF_NONE); + state.EnableTexture(true); + } + state.SetStencil(0, SOP_Keep, SF_AllOn); +} + + + //----------------------------------------------------------------------------- // // @@ -245,7 +407,7 @@ int HWLinePortal::ClipPoint(const DVector2 &pos) // //----------------------------------------------------------------------------- -bool HWMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper) +bool HWMirrorPortal::Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) { auto state = mOwner->mState; if (state->renderdepth > r_mirror_recursions) @@ -315,7 +477,7 @@ bool HWMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper) state->MirrorFlag++; di->SetClipLine(linedef); - di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); + di->SetupView(rstate, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); clipper->Clear(); @@ -326,7 +488,7 @@ bool HWMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper) return true; } -void HWMirrorPortal::Shutdown(HWDrawInfo *di) +void HWMirrorPortal::Shutdown(HWDrawInfo *di, FRenderState &rstate) { mOwner->mState->MirrorFlag--; } @@ -348,7 +510,7 @@ const char *HWMirrorPortal::GetName() { return "Mirror"; } // // //----------------------------------------------------------------------------- -bool HWLineToLinePortal::Setup(HWDrawInfo *di, Clipper *clipper) +bool HWLineToLinePortal::Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) { // TODO: Handle recursion more intelligently auto &state = mOwner->mState; @@ -395,7 +557,7 @@ bool HWLineToLinePortal::Setup(HWDrawInfo *di, Clipper *clipper) vp.ViewActor = nullptr; di->SetClipLine(glport->lines[0]->mDestination); - di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); + di->SetupView(rstate, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); ClearClipper(di, clipper); return true; @@ -425,7 +587,7 @@ const char *HWLineToLinePortal::GetName() { return "LineToLine"; } // //----------------------------------------------------------------------------- -bool HWSkyboxPortal::Setup(HWDrawInfo *di, Clipper *clipper) +bool HWSkyboxPortal::Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) { auto state = mOwner->mState; old_pm = state->PlaneMirrorMode; @@ -444,8 +606,7 @@ bool HWSkyboxPortal::Setup(HWDrawInfo *di, Clipper *clipper) portal->mFlags |= PORTSF_INSKYBOX; vp.extralight = 0; - - oldclamp = di->SetDepthClamp(false); + oldclamp = rstate.SetDepthClamp(false); vp.Pos = origin->InterpolatedPosition(vp.TicFrac); vp.ActorPos = origin->Pos(); vp.Angles.Yaw += (origin->PrevAngles.Yaw + deltaangle(origin->PrevAngles.Yaw, origin->Angles.Yaw) * vp.TicFrac); @@ -458,7 +619,7 @@ bool HWSkyboxPortal::Setup(HWDrawInfo *di, Clipper *clipper) vp.ViewActor = origin; - di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); + di->SetupView(rstate, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); di->SetViewArea(); ClearClipper(di, clipper); di->UpdateCurrentMapSection(); @@ -466,11 +627,12 @@ bool HWSkyboxPortal::Setup(HWDrawInfo *di, Clipper *clipper) } -void HWSkyboxPortal::Shutdown(HWDrawInfo *di) +void HWSkyboxPortal::Shutdown(HWDrawInfo *di, FRenderState &rstate) { + rstate.SetDepthClamp(oldclamp); + auto state = mOwner->mState; portal->mFlags &= ~PORTSF_INSKYBOX; - di->SetDepthClamp(oldclamp); state->inskybox = false; state->skyboxrecursion--; state->PlaneMirrorMode = old_pm; @@ -535,7 +697,7 @@ void HWSectorStackPortal::SetupCoverage(HWDrawInfo *di) // GLSectorStackPortal::DrawContents // //----------------------------------------------------------------------------- -bool HWSectorStackPortal::Setup(HWDrawInfo *di, Clipper *clipper) +bool HWSectorStackPortal::Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) { auto state = mOwner->mState; FSectorPortalGroup *portal = origin; @@ -548,7 +710,7 @@ bool HWSectorStackPortal::Setup(HWDrawInfo *di, Clipper *clipper) // avoid recursions! if (origin->plane != -1) screen->instack[origin->plane]++; - di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); + di->SetupView(rstate, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); SetupCoverage(di); ClearClipper(di, clipper); @@ -564,7 +726,7 @@ bool HWSectorStackPortal::Setup(HWDrawInfo *di, Clipper *clipper) } -void HWSectorStackPortal::Shutdown(HWDrawInfo *di) +void HWSectorStackPortal::Shutdown(HWDrawInfo *di, FRenderState &rstate) { if (origin->plane != -1) screen->instack[origin->plane]--; } @@ -587,7 +749,7 @@ const char *HWSectorStackPortal::GetName() { return "Sectorstack"; } // //----------------------------------------------------------------------------- -bool HWPlaneMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper) +bool HWPlaneMirrorPortal::Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) { auto state = mOwner->mState; if (state->renderdepth > r_mirror_recursions) @@ -610,14 +772,14 @@ bool HWPlaneMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper) state->PlaneMirrorFlag++; di->SetClipHeight(planez, state->PlaneMirrorMode < 0 ? -1.f : 1.f); - di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); + di->SetupView(rstate, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1)); ClearClipper(di, clipper); di->UpdateCurrentMapSection(); return true; } -void HWPlaneMirrorPortal::Shutdown(HWDrawInfo *di) +void HWPlaneMirrorPortal::Shutdown(HWDrawInfo *di, FRenderState &rstate) { auto state = mOwner->mState; state->PlaneMirrorFlag--; @@ -626,3 +788,198 @@ void HWPlaneMirrorPortal::Shutdown(HWDrawInfo *di) } const char *HWPlaneMirrorPortal::GetName() { return origin->fC() < 0? "Planemirror ceiling" : "Planemirror floor"; } + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// +// +// Horizon Portal +// +// +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +HWHorizonPortal::HWHorizonPortal(FPortalSceneState *s, GLHorizonInfo * pt, FRenderViewpoint &vp, HWDrawInfo *di, bool local) + : HWPortal(s, local) +{ + origin = pt; + + // create the vertex data for this horizon portal. + GLSectorPlane * sp = &origin->plane; + const float vx = vp.Pos.X; + const float vy = vp.Pos.Y; + const float vz = vp.Pos.Z; + const float z = sp->Texheight; + const float tz = (z - vz); + + // Draw to some far away boundary + // This is not drawn as larger strips because it causes visual glitches. + auto verts = screen->mVertexData->AllocVertices(1024 + 10); + auto ptr = verts.first; + for (int xx = -32768; xx < 32768; xx += 4096) + { + float x = xx + vx; + for (int yy = -32768; yy < 32768; yy += 4096) + { + float y = yy + vy; + ptr->Set(x, z, y, x / 64, -y / 64); + ptr++; + ptr->Set(x + 4096, z, y, x / 64 + 64, -y / 64); + ptr++; + ptr->Set(x, z, y + 4096, x / 64, -y / 64 - 64); + ptr++; + ptr->Set(x + 4096, z, y + 4096, x / 64 + 64, -y / 64 - 64); + ptr++; + } + } + + // fill the gap between the polygon and the true horizon + // Since I can't draw into infinity there can always be a + // small gap + ptr->Set(-32768 + vx, z, -32768 + vy, 512.f, 0); + ptr++; + ptr->Set(-32768 + vx, vz, -32768 + vy, 512.f, tz); + ptr++; + ptr->Set(-32768 + vx, z, 32768 + vy, -512.f, 0); + ptr++; + ptr->Set(-32768 + vx, vz, 32768 + vy, -512.f, tz); + ptr++; + ptr->Set(32768 + vx, z, 32768 + vy, 512.f, 0); + ptr++; + ptr->Set(32768 + vx, vz, 32768 + vy, 512.f, tz); + ptr++; + ptr->Set(32768 + vx, z, -32768 + vy, -512.f, 0); + ptr++; + ptr->Set(32768 + vx, vz, -32768 + vy, -512.f, tz); + ptr++; + ptr->Set(-32768 + vx, z, -32768 + vy, 512.f, 0); + ptr++; + ptr->Set(-32768 + vx, vz, -32768 + vy, 512.f, tz); + ptr++; + + voffset = verts.second; + vcount = 1024; + +} + +//----------------------------------------------------------------------------- +// +// HWHorizonPortal::DrawContents +// +//----------------------------------------------------------------------------- +void HWHorizonPortal::DrawContents(HWDrawInfo *di, FRenderState &state) +{ + Clocker c(PortalAll); + + FMaterial * gltexture; + player_t * player = &players[consoleplayer]; + GLSectorPlane * sp = &origin->plane; + auto &vp = di->Viewpoint; + + gltexture = FMaterial::ValidateTexture(sp->texture, false, true); + if (!gltexture) + { + state.ClearScreen(); + return; + } + di->SetCameraPos(vp.Pos); + + + if (gltexture && gltexture->tex->isFullbright()) + { + // glowing textures are always drawn full bright without color + state.SetColor(255, 0, false, origin->colormap, 1.f); + state.SetFog(255, 0, false, &origin->colormap, false); + } + else + { + int rel = getExtraLight(); + state.SetColor(origin->lightlevel, rel, di->isFullbrightScene(), origin->colormap, 1.0f); + state.SetFog(origin->lightlevel, rel, di->isFullbrightScene(), &origin->colormap, false); + } + + + state.SetMaterial(gltexture, CLAMP_NONE, 0, -1); + state.SetObjectColor(origin->specialcolor); + + state.SetPlaneTextureRotation(sp, gltexture); + state.AlphaFunc(Alpha_GEqual, 0.f); + state.SetRenderStyle(STYLE_Source); + + for (unsigned i = 0; i < vcount; i += 4) + { + state.Draw(DT_TriangleStrip, voffset + i, 4, true);// i == 0); + } + state.Draw(DT_TriangleStrip, voffset + vcount, 10, false); + + state.EnableTextureMatrix(false); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// +// +// Eternity-style horizon portal +// +// To the rest of the engine these masquerade as a skybox portal +// Internally they need to draw two horizon or sky portals +// and will use the respective classes to achieve that. +// +// +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +void HWEEHorizonPortal::DrawContents(HWDrawInfo *di, FRenderState &state) +{ + auto &vp = di->Viewpoint; + sector_t *sector = portal->mOrigin; + if (sector->GetTexture(sector_t::floor) == skyflatnum || + sector->GetTexture(sector_t::ceiling) == skyflatnum) + { + GLSkyInfo skyinfo; + skyinfo.init(sector->sky, 0); + HWSkyPortal sky(screen->mSkyData, mState, &skyinfo, true); + sky.DrawContents(di, state); + } + if (sector->GetTexture(sector_t::ceiling) != skyflatnum) + { + GLHorizonInfo horz; + horz.plane.GetFromSector(sector, sector_t::ceiling); + horz.lightlevel = hw_ClampLight(sector->GetCeilingLight()); + horz.colormap = sector->Colormap; + horz.specialcolor = 0xffffffff; + if (portal->mType == PORTS_PLANE) + { + horz.plane.Texheight = vp.Pos.Z + fabs(horz.plane.Texheight); + } + HWHorizonPortal ceil(mState, &horz, di->Viewpoint, di, true); + ceil.DrawContents(di, state); + } + if (sector->GetTexture(sector_t::floor) != skyflatnum) + { + GLHorizonInfo horz; + horz.plane.GetFromSector(sector, sector_t::floor); + horz.lightlevel = hw_ClampLight(sector->GetFloorLight()); + horz.colormap = sector->Colormap; + horz.specialcolor = 0xffffffff; + if (portal->mType == PORTS_PLANE) + { + horz.plane.Texheight = vp.Pos.Z - fabs(horz.plane.Texheight); + } + HWHorizonPortal floor(mState, &horz, di->Viewpoint, di, true); + floor.DrawContents(di, state); + } +} + +const char *HWHorizonPortal::GetName() { return "Horizon"; } +const char *HWEEHorizonPortal::GetName() { return "EEHorizon"; } + + diff --git a/src/hwrenderer/scene/hw_portal.h b/src/hwrenderer/scene/hw_portal.h index d3228ab1d..35629b89a 100644 --- a/src/hwrenderer/scene/hw_portal.h +++ b/src/hwrenderer/scene/hw_portal.h @@ -1,9 +1,13 @@ #pragma once #include "portal.h" +#include "actor.h" #include "hw_drawinfo.h" #include "hw_drawstructs.h" #include "hwrenderer/textures/hw_material.h" +#include "hwrenderer/scene/hw_renderstate.h" + +class FSkyVertexBuffer; struct GLSkyInfo { @@ -37,50 +41,50 @@ struct GLHorizonInfo struct FPortalSceneState; -class IPortal +class HWPortal { friend struct FPortalSceneState; + + enum + { + STP_Stencil, + STP_DepthClear, + STP_DepthRestore, + STP_AllInOne + }; + + ActorRenderFlags savedvisibility; + TArray mPrimIndices; + + void DrawPortalStencil(FRenderState &state, int pass); + public: FPortalSceneState * mState; TArray lines; - IPortal(FPortalSceneState *s, bool local); - virtual ~IPortal() {} + HWPortal(FPortalSceneState *s, bool local); + void SetupStencil(HWDrawInfo *di, FRenderState &state, bool usestencil); + void RemoveStencil(HWDrawInfo *di, FRenderState &state, bool usestencil); + + virtual ~HWPortal() {} virtual void * GetSource() const = 0; // GetSource MUST be implemented! virtual const char *GetName() = 0; virtual bool IsSky() { return false; } virtual bool NeedCap() { return true; } virtual bool NeedDepthBuffer() { return true; } - virtual void DrawContents(HWDrawInfo *di) = 0; + virtual void DrawContents(HWDrawInfo *di, FRenderState &state) = 0; virtual void RenderAttached(HWDrawInfo *di) {} - virtual bool Start(bool usestencil, bool doquery, HWDrawInfo *outer_di, HWDrawInfo **pDi) = 0; - virtual void End(HWDrawInfo *di, bool usestencil) = 0; void AddLine(GLWall * l) { lines.Push(*l); } - void RenderPortal(bool usestencil, bool doquery, HWDrawInfo *outer_di) - { - // Start may perform an occlusion query. If that returns 0 there - // is no need to draw the stencil's contents and there's also no - // need to restore the affected area becasue there is none! - HWDrawInfo *di; - if (Start(usestencil, doquery, outer_di, &di)) - { - DrawContents(di); - End(di, usestencil); - } - } - }; struct FPortalSceneState { - int recursion = 0; - int MirrorFlag = 0; int PlaneMirrorFlag = 0; int renderdepth = 0; @@ -101,24 +105,18 @@ struct FPortalSceneState UniquePlaneMirrors.Clear(); } - int GetRecursion() const - { - return recursion; - } - bool isMirrored() const { return !!((MirrorFlag ^ PlaneMirrorFlag) & 1); } void StartFrame(); - bool RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di); - void EndFrame(HWDrawInfo *outer_di); - - + bool RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di, FRenderState &state); + void EndFrame(HWDrawInfo *outer_di, FRenderState &state); + void RenderPortal(HWPortal *p, FRenderState &state, bool usestencil, HWDrawInfo *outer_di); }; -inline IPortal::IPortal(FPortalSceneState *s, bool local) : mState(s) +inline HWPortal::HWPortal(FPortalSceneState *s, bool local) : mState(s) { //if (!local) s->portals.Push(this); } @@ -127,11 +125,11 @@ inline IPortal::IPortal(FPortalSceneState *s, bool local) : mState(s) class HWScenePortalBase { protected: - IPortal *mOwner; + HWPortal *mOwner; public: HWScenePortalBase() {} virtual ~HWScenePortalBase() {} - void SetOwner(IPortal *p) { mOwner = p; } + void SetOwner(HWPortal *p) { mOwner = p; } void ClearClipper(HWDrawInfo *di, Clipper *clipper); virtual int ClipSeg(seg_t *seg, const DVector3 &viewpos) { return PClip_Inside; } @@ -145,8 +143,8 @@ public: virtual void * GetSource() const = 0; // GetSource MUST be implemented! virtual const char *GetName() = 0; - virtual bool Setup(HWDrawInfo *di, Clipper *clipper) = 0; - virtual void Shutdown(HWDrawInfo *di) {} + virtual bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) = 0; + virtual void Shutdown(HWDrawInfo *di, FRenderState &rstate) {} virtual void RenderAttached(HWDrawInfo *di) {} }; @@ -207,8 +205,8 @@ struct HWMirrorPortal : public HWLinePortal line_t * linedef; protected: - bool Setup(HWDrawInfo *di, Clipper *clipper) override; - void Shutdown(HWDrawInfo *di) override; + bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override; + void Shutdown(HWDrawInfo *di, FRenderState &rstate) override; void * GetSource() const override { return linedef; } const char *GetName() override; @@ -226,7 +224,7 @@ struct HWLineToLinePortal : public HWLinePortal { FLinePortalSpan *glport; protected: - bool Setup(HWDrawInfo *di, Clipper *clipper) override; + bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override; virtual void * GetSource() const override { return glport; } virtual const char *GetName() override; virtual line_t *ClipLine() override { return line(); } @@ -249,8 +247,8 @@ struct HWSkyboxPortal : public HWScenePortalBase FSectorPortal * portal; protected: - bool Setup(HWDrawInfo *di, Clipper *clipper) override; - void Shutdown(HWDrawInfo *di) override; + bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override; + void Shutdown(HWDrawInfo *di, FRenderState &rstate) override; virtual void * GetSource() const { return portal; } virtual bool IsSky() { return true; } virtual const char *GetName(); @@ -270,8 +268,8 @@ struct HWSectorStackPortal : public HWScenePortalBase { TArray subsectors; protected: - bool Setup(HWDrawInfo *di, Clipper *clipper) override; - void Shutdown(HWDrawInfo *di) override; + bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override; + void Shutdown(HWDrawInfo *di, FRenderState &rstate) override; virtual void * GetSource() const { return origin; } virtual bool IsSky() { return true; } // although this isn't a real sky it can be handled as one. virtual const char *GetName(); @@ -295,8 +293,8 @@ struct HWPlaneMirrorPortal : public HWScenePortalBase { int old_pm; protected: - bool Setup(HWDrawInfo *di, Clipper *clipper) override; - void Shutdown(HWDrawInfo *di) override; + bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override; + void Shutdown(HWDrawInfo *di, FRenderState &rstate) override; virtual void * GetSource() const { return origin; } virtual const char *GetName(); secplane_t * origin; @@ -310,3 +308,100 @@ public: }; + +class HWScenePortal : public HWPortal +{ +public: + HWScenePortalBase *mScene; + HWScenePortal(FPortalSceneState *state, HWScenePortalBase *handler) : HWPortal(state, false) + { + mScene = handler; + handler->SetOwner(this); + } + ~HWScenePortal() { delete mScene; } + virtual void * GetSource() const { return mScene->GetSource(); } + virtual const char *GetName() { return mScene->GetName(); } + virtual bool IsSky() { return mScene->IsSky(); } + virtual bool NeedCap() { return true; } + virtual bool NeedDepthBuffer() { return true; } + virtual void DrawContents(HWDrawInfo *di, FRenderState &state) + { + if (mScene->Setup(di, state, di->mClipper)) + { + di->DrawScene(di, DM_PORTAL); + mScene->Shutdown(di, state); + } + else state.ClearScreen(); + } + virtual void RenderAttached(HWDrawInfo *di) { return mScene->RenderAttached(di); } +}; + + +struct HWHorizonPortal : public HWPortal +{ + GLHorizonInfo * origin; + unsigned int voffset; + unsigned int vcount; + friend struct HWEEHorizonPortal; + +protected: + virtual void DrawContents(HWDrawInfo *di, FRenderState &state); + virtual void * GetSource() const { return origin; } + virtual bool NeedDepthBuffer() { return false; } + virtual bool NeedCap() { return false; } + virtual const char *GetName(); + +public: + + HWHorizonPortal(FPortalSceneState *state, GLHorizonInfo * pt, FRenderViewpoint &vp, HWDrawInfo *di, bool local = false); +}; + +struct HWEEHorizonPortal : public HWPortal +{ + FSectorPortal * portal; + +protected: + virtual void DrawContents(HWDrawInfo *di, FRenderState &state); + virtual void * GetSource() const { return portal; } + virtual bool NeedDepthBuffer() { return false; } + virtual bool NeedCap() { return false; } + virtual const char *GetName(); + +public: + + HWEEHorizonPortal(FPortalSceneState *state, FSectorPortal *pt, HWDrawInfo *di) : HWPortal(state, false) + { + portal = pt; + } + +}; + + +struct HWSkyPortal : public HWPortal +{ + GLSkyInfo * origin; + FSkyVertexBuffer *vertexBuffer; + friend struct HWEEHorizonPortal; + + void RenderRow(HWDrawInfo *di, FRenderState &state, EDrawType prim, int row, bool apply = true); + void RenderBox(HWDrawInfo *di, FRenderState &state, FTextureID texno, FMaterial * gltex, float x_offset, bool sky2); + void RenderDome(HWDrawInfo *di, FRenderState &state, FMaterial * tex, float x_offset, float y_offset, bool mirror, int mode); + +protected: + virtual void DrawContents(HWDrawInfo *di, FRenderState &state); + virtual void * GetSource() const { return origin; } + virtual bool IsSky() { return true; } + virtual bool NeedDepthBuffer() { return false; } + virtual const char *GetName(); + +public: + + + HWSkyPortal(FSkyVertexBuffer *vertexbuffer, FPortalSceneState *state, GLSkyInfo * pt, bool local = false) + : HWPortal(state, local) + { + origin = pt; + vertexBuffer = vertexbuffer; + } + +}; diff --git a/src/hwrenderer/scene/hw_renderhacks.cpp b/src/hwrenderer/scene/hw_renderhacks.cpp index 52f996f41..f6899b434 100644 --- a/src/hwrenderer/scene/hw_renderhacks.cpp +++ b/src/hwrenderer/scene/hw_renderhacks.cpp @@ -29,13 +29,78 @@ #include "r_utility.h" #include "r_sky.h" #include "g_levellocals.h" +#include "a_dynlight.h" #include "hw_drawinfo.h" #include "hw_drawstructs.h" #include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hwrenderer/scene/hw_portal.h" sector_t * hw_FakeFlat(sector_t * sec, sector_t * dest, area_t in_area, bool back); +//========================================================================== +// +// light setup for render hacks. +// This can ignore many of the special checks because +// this will never be translucent and never be part of a portal or a 3D floor +// +//========================================================================== + +int HWDrawInfo::SetupLightsForOtherPlane(subsector_t * sub, FDynLightData &lightdata, const secplane_t *plane) +{ + if (level.HasDynamicLights && !isFullbrightScene()) + { + Plane p; + FLightNode * node = sub->lighthead; + + lightdata.Clear(); + while (node) + { + ADynamicLight * light = node->lightsource; + + if (light->flags2&MF2_DORMANT) + { + node = node->nextLight; + continue; + } + iter_dlightf++; + + p.Set(plane->Normal(), plane->fD()); + draw_dlightf += lightdata.GetLight(sub->sector->PortalGroup, p, light, true); + node = node->nextLight; + } + + return screen->mLights->UploadLights(lightdata); + } + else return -1; +} + +//========================================================================== +// +// Vertex setup for extra planes +// +//========================================================================== + +int HWDrawInfo::CreateOtherPlaneVertices(subsector_t *sub, const secplane_t *plane) +{ + auto alloc = screen->mVertexData->AllocVertices(sub->numlines); + auto ptr = alloc.first; + for (unsigned int k = 0; k < sub->numlines; k++) + { + vertex_t *vt = sub->firstline[k].v1; + ptr->x = vt->fX(); + ptr->z = plane->ZatPoint(vt); + ptr->y = vt->fY(); + ptr->u = vt->fX() / 64.f; + ptr->v = -vt->fY() / 64.f; + ptr++; + } + return alloc.second; +} + //========================================================================== // // Adds a subsector plane to a sector's render list @@ -46,12 +111,14 @@ void HWDrawInfo::AddOtherFloorPlane(int sector, gl_subsectorrendernode * node) { int oldcnt = otherfloorplanes.Size(); - if (oldcnt<=sector) + if (oldcnt <= sector) { - otherfloorplanes.Resize(sector+1); - for(int i=oldcnt;i<=sector;i++) otherfloorplanes[i]=NULL; + otherfloorplanes.Resize(sector + 1); + for (int i = oldcnt; i <= sector; i++) otherfloorplanes[i] = nullptr; } node->next = otherfloorplanes[sector]; + node->lightindex = SetupLightsForOtherPlane(node->sub, lightdata, &level.sectors[sector].floorplane); + node->vertexindex = CreateOtherPlaneVertices(node->sub, &level.sectors[sector].floorplane); otherfloorplanes[sector] = node; } @@ -59,12 +126,14 @@ void HWDrawInfo::AddOtherCeilingPlane(int sector, gl_subsectorrendernode * node) { int oldcnt = otherceilingplanes.Size(); - if (oldcnt<=sector) + if (oldcnt <= sector) { - otherceilingplanes.Resize(sector+1); - for(int i=oldcnt;i<=sector;i++) otherceilingplanes[i]=NULL; + otherceilingplanes.Resize(sector + 1); + for (int i = oldcnt; i <= sector; i++) otherceilingplanes[i] = nullptr; } node->next = otherceilingplanes[sector]; + node->lightindex = SetupLightsForOtherPlane(node->sub, lightdata, &level.sectors[sector].ceilingplane); + node->vertexindex = CreateOtherPlaneVertices(node->sub, &level.sectors[sector].ceilingplane); otherceilingplanes[sector] = node; } @@ -566,54 +635,229 @@ void HWDrawInfo::HandleMissingTextures(area_t in_area) } } - //========================================================================== // // // //========================================================================== -void HWDrawInfo::DrawUnhandledMissingTextures() +void HWDrawInfo::CreateFloodStencilPoly(wallseg * ws, FFlatVertex *vertices) { - validcount++; - for (int i = MissingUpperSegs.Size() - 1; i >= 0; i--) + vertices[0].Set(ws->x1, ws->z1, ws->y1, 0, 0); + vertices[1].Set(ws->x1, ws->z2, ws->y1, 0, 0); + vertices[2].Set(ws->x2, ws->z2, ws->y2, 0, 0); + vertices[3].Set(ws->x2, ws->z1, ws->y2, 0, 0); +} + +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::CreateFloodPoly(wallseg * ws, FFlatVertex *vertices, float planez, sector_t * sec, bool ceiling) +{ + float fviewx = Viewpoint.Pos.X; + float fviewy = Viewpoint.Pos.Y; + float fviewz = Viewpoint.Pos.Z; + + float prj_fac1 = (planez - fviewz) / (ws->z1 - fviewz); + float prj_fac2 = (planez - fviewz) / (ws->z2 - fviewz); + + float px1 = fviewx + prj_fac1 * (ws->x1 - fviewx); + float py1 = fviewy + prj_fac1 * (ws->y1 - fviewy); + + float px2 = fviewx + prj_fac2 * (ws->x1 - fviewx); + float py2 = fviewy + prj_fac2 * (ws->y1 - fviewy); + + float px3 = fviewx + prj_fac2 * (ws->x2 - fviewx); + float py3 = fviewy + prj_fac2 * (ws->y2 - fviewy); + + float px4 = fviewx + prj_fac1 * (ws->x2 - fviewx); + float py4 = fviewy + prj_fac1 * (ws->y2 - fviewy); + + vertices[0].Set(px1, planez, py1, px1 / 64, -py1 / 64); + vertices[1].Set(px2, planez, py2, px2 / 64, -py2 / 64); + vertices[2].Set(px3, planez, py3, px3 / 64, -py3 / 64); + vertices[3].Set(px4, planez, py4, px4 / 64, -py4 / 64); +} + +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::PrepareUpperGap(seg_t * seg) +{ + wallseg ws; + sector_t ffake, bfake; + sector_t * fakefsector = hw_FakeFlat(seg->frontsector, &ffake, in_area, true); + sector_t * fakebsector = hw_FakeFlat(seg->backsector, &bfake, in_area, false); + + vertex_t * v1, *v2; + + // Although the plane can be sloped this code will only be called + // when the edge itself is not. + double backz = fakebsector->ceilingplane.ZatPoint(seg->v1); + double frontz = fakefsector->ceilingplane.ZatPoint(seg->v1); + + if (fakebsector->GetTexture(sector_t::ceiling) == skyflatnum) return; + if (backz < Viewpoint.Pos.Z) return; + + if (seg->sidedef == seg->linedef->sidedef[0]) { - int index = MissingUpperSegs[i].MTI_Index; - if (index >= 0 && MissingUpperTextures[index].seg == NULL) continue; - - seg_t * seg = MissingUpperSegs[i].seg; - - // already done! - if (seg->linedef->validcount == validcount) continue; // already done - seg->linedef->validcount = validcount; - if (seg->frontsector->GetPlaneTexZ(sector_t::ceiling) < Viewpoint.Pos.Z) continue; // out of sight - - // FIXME: The check for degenerate subsectors should be more precise - if (seg->PartnerSeg && (seg->PartnerSeg->Subsector->flags & SSECF_DEGENERATE)) continue; - if (seg->backsector->transdoor) continue; - if (seg->backsector->GetTexture(sector_t::ceiling) == skyflatnum) continue; - if (seg->backsector->ValidatePortal(sector_t::ceiling) != NULL) continue; - - if (!level.notexturefill) FloodUpperGap(seg); + v1 = seg->linedef->v1; + v2 = seg->linedef->v2; + } + else + { + v1 = seg->linedef->v2; + v2 = seg->linedef->v1; } - validcount++; - for (int i = MissingLowerSegs.Size() - 1; i >= 0; i--) + ws.x1 = v1->fX(); + ws.y1 = v1->fY(); + ws.x2 = v2->fX(); + ws.y2 = v2->fY(); + + ws.z1 = frontz; + ws.z2 = backz; + + auto vertices = screen->mVertexData->AllocVertices(8); + + CreateFloodStencilPoly(&ws, vertices.first); + CreateFloodPoly(&ws, vertices.first+4, ws.z2, fakebsector, true); + + gl_floodrendernode *node = new gl_floodrendernode; + int oldcnt = floodfloorsegs.Size(); + auto sector = fakebsector->sectornum; + if (oldcnt <= sector) { - int index = MissingLowerSegs[i].MTI_Index; - if (index >= 0 && MissingLowerTextures[index].seg == NULL) continue; + floodfloorsegs.Resize(sector + 1); + for (int i = oldcnt; i <= sector; i++) floodfloorsegs[i] = nullptr; + } - seg_t * seg = MissingLowerSegs[i].seg; + node->next = floodfloorsegs[sector]; + node->seg = seg; + node->vertexindex = vertices.second; + floodfloorsegs[sector] = node; +} - if (seg->linedef->validcount == validcount) continue; // already done - seg->linedef->validcount = validcount; - if (!(sectorrenderflags[seg->backsector->sectornum] & SSRF_RENDERFLOOR)) continue; - if (seg->frontsector->GetPlaneTexZ(sector_t::floor) > Viewpoint.Pos.Z) continue; // out of sight - if (seg->backsector->transdoor) continue; - if (seg->backsector->GetTexture(sector_t::floor) == skyflatnum) continue; - if (seg->backsector->ValidatePortal(sector_t::floor) != NULL) continue; - if (!level.notexturefill) FloodLowerGap(seg); +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::PrepareLowerGap(seg_t * seg) +{ + wallseg ws; + sector_t ffake, bfake; + sector_t * fakefsector = hw_FakeFlat(seg->frontsector, &ffake, in_area, true); + sector_t * fakebsector = hw_FakeFlat(seg->backsector, &bfake, in_area, false); + + vertex_t * v1, *v2; + + // Although the plane can be sloped this code will only be called + // when the edge itself is not. + double backz = fakebsector->floorplane.ZatPoint(seg->v1); + double frontz = fakefsector->floorplane.ZatPoint(seg->v1); + + + if (fakebsector->GetTexture(sector_t::floor) == skyflatnum) return; + if (fakebsector->GetPlaneTexZ(sector_t::floor) > Viewpoint.Pos.Z) return; + + if (seg->sidedef == seg->linedef->sidedef[0]) + { + v1 = seg->linedef->v1; + v2 = seg->linedef->v2; + } + else + { + v1 = seg->linedef->v2; + v2 = seg->linedef->v1; + } + + ws.x1 = v1->fX(); + ws.y1 = v1->fY(); + ws.x2 = v2->fX(); + ws.y2 = v2->fY(); + + ws.z2 = frontz; + ws.z1 = backz; + + auto vertices = screen->mVertexData->AllocVertices(8); + + CreateFloodStencilPoly(&ws, vertices.first); + CreateFloodPoly(&ws, vertices.first+4, ws.z1, fakebsector, false); + + gl_floodrendernode *node = new gl_floodrendernode; + int oldcnt = floodceilingsegs.Size(); + auto sector = fakebsector->sectornum; + if (oldcnt <= sector) + { + floodceilingsegs.Resize(sector + 1); + for (int i = oldcnt; i <= sector; i++) floodceilingsegs[i] = nullptr; + } + + node->next = floodceilingsegs[sector]; + node->seg = seg; + node->vertexindex = vertices.second; + floodceilingsegs[sector] = node; +} + +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::PrepareUnhandledMissingTextures() +{ + if (!level.notexturefill) + { + validcount++; + for (int i = MissingUpperSegs.Size() - 1; i >= 0; i--) + { + int index = MissingUpperSegs[i].MTI_Index; + if (index >= 0 && MissingUpperTextures[index].seg == NULL) continue; + + seg_t * seg = MissingUpperSegs[i].seg; + + // already done! + if (seg->linedef->validcount == validcount) continue; // already done + seg->linedef->validcount = validcount; + if (seg->frontsector->GetPlaneTexZ(sector_t::ceiling) < Viewpoint.Pos.Z) continue; // out of sight + + // FIXME: The check for degenerate subsectors should be more precise + if (seg->PartnerSeg && (seg->PartnerSeg->Subsector->flags & SSECF_DEGENERATE)) continue; + if (seg->backsector->transdoor) continue; + if (seg->backsector->GetTexture(sector_t::ceiling) == skyflatnum) continue; + if (seg->backsector->ValidatePortal(sector_t::ceiling) != NULL) continue; + + PrepareUpperGap(seg); + } + + validcount++; + for (int i = MissingLowerSegs.Size() - 1; i >= 0; i--) + { + int index = MissingLowerSegs[i].MTI_Index; + if (index >= 0 && MissingLowerTextures[index].seg == NULL) continue; + + seg_t * seg = MissingLowerSegs[i].seg; + + if (seg->linedef->validcount == validcount) continue; // already done + seg->linedef->validcount = validcount; + if (!(sectorrenderflags[seg->backsector->sectornum] & SSRF_RENDERFLOOR)) continue; + if (seg->frontsector->GetPlaneTexZ(sector_t::floor) > Viewpoint.Pos.Z) continue; // out of sight + if (seg->backsector->transdoor) continue; + if (seg->backsector->GetTexture(sector_t::floor) == skyflatnum) continue; + if (seg->backsector->ValidatePortal(sector_t::floor) != NULL) continue; + + PrepareLowerGap(seg); + } } MissingUpperTextures.Clear(); MissingLowerTextures.Clear(); @@ -1122,3 +1366,21 @@ void HWDrawInfo::ProcessSectorStacks(area_t in_area) CeilingStacks.Clear(); } +//========================================================================== +// +// +// +//========================================================================== + +void HWDrawInfo::AddSubsectorToPortal(FSectorPortalGroup *ptg, subsector_t *sub) +{ + auto portal = FindPortal(ptg); + if (!portal) + { + portal = new HWScenePortal(screen->mPortalState, new HWSectorStackPortal(ptg)); + Portals.Push(portal); + } + auto ptl = static_cast(static_cast(portal)->mScene); + ptl->AddSubsector(sub); +} + diff --git a/src/hwrenderer/scene/hw_renderstate.cpp b/src/hwrenderer/scene/hw_renderstate.cpp new file mode 100644 index 000000000..5aa8d9c3c --- /dev/null +++ b/src/hwrenderer/scene/hw_renderstate.cpp @@ -0,0 +1,156 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2000-2018 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** hw_renderstate.cpp +** hardware independent part of render state. +** +*/ + +#include "hw_renderstate.h" +#include "hw_drawstructs.h" +#include "hw_portal.h" +#include "hwrenderer/utility/hw_lighting.h" +#include "hwrenderer/utility/hw_cvars.h" + + +//========================================================================== +// +// set current light color +// +//========================================================================== +void FRenderState::SetColor(int sectorlightlevel, int rellight, bool fullbright, const FColormap &cm, float alpha, bool weapon) +{ + if (fullbright) + { + SetColorAlpha(0xffffff, alpha, 0); + SetSoftLightLevel(255); + } + else + { + int hwlightlevel = hw_CalcLightLevel(sectorlightlevel, rellight, weapon, cm.BlendFactor); + PalEntry pe = hw_CalcLightColor(hwlightlevel, cm.LightColor, cm.BlendFactor); + SetColorAlpha(pe, alpha, cm.Desaturation); + SetSoftLightLevel(hw_ClampLight(sectorlightlevel + rellight), cm.BlendFactor); + } +} + +//========================================================================== +// +// Lighting stuff +// +//========================================================================== + +void FRenderState::SetShaderLight(float level, float olight) +{ + const float MAXDIST = 256.f; + const float THRESHOLD = 96.f; + const float FACTOR = 0.75f; + + if (level > 0) + { + float lightdist, lightfactor; + + if (olight < THRESHOLD) + { + lightdist = (MAXDIST / 2) + (olight * MAXDIST / THRESHOLD / 2); + olight = THRESHOLD; + } + else lightdist = MAXDIST; + + lightfactor = 1.f + ((olight / level) - 1.f) * FACTOR; + if (lightfactor == 1.f) lightdist = 0.f; // save some code in the shader + SetLightParms(lightfactor, lightdist); + } + else + { + SetLightParms(1.f, 0.f); + } +} + + +//========================================================================== +// +// Sets the fog for the current polygon +// +//========================================================================== + +void FRenderState::SetFog(int lightlevel, int rellight, bool fullbright, const FColormap *cmap, bool isadditive) +{ + PalEntry fogcolor; + float fogdensity; + + if (level.flags&LEVEL_HASFADETABLE) + { + fogdensity = 70; + fogcolor = 0x808080; + } + else if (cmap != NULL && !fullbright) + { + fogcolor = cmap->FadeColor; + fogdensity = hw_GetFogDensity(lightlevel, fogcolor, cmap->FogDensity, cmap->BlendFactor); + fogcolor.a = 0; + } + else + { + fogcolor = 0; + fogdensity = 0; + } + + // Make fog a little denser when inside a skybox + if (screen->mPortalState->inskybox) fogdensity += fogdensity / 2; + + + // no fog in enhanced vision modes! + if (fogdensity == 0 || gl_fogmode == 0) + { + EnableFog(false); + SetFog(0, 0); + } + else + { + if ((level.lightmode == 2 || (level.lightmode == 8 && cmap->BlendFactor > 0)) && fogcolor == 0) + { + float light = (float)hw_CalcLightLevel(lightlevel, rellight, false, cmap->BlendFactor); + SetShaderLight(light, lightlevel); + } + else + { + SetLightParms(1.f, 0.f); + } + + // For additive rendering using the regular fog color here would mean applying it twice + // so always use black + if (isadditive) + { + fogcolor = 0; + } + + EnableFog(true); + SetFog(fogcolor, fogdensity); + + // Korshun: fullbright fog like in software renderer. + if (level.lightmode == 8 && cmap->BlendFactor == 0 && level.brightfog && fogdensity != 0 && fogcolor != 0) + { + SetSoftLightLevel(255); + } + } +} diff --git a/src/hwrenderer/scene/hw_renderstate.h b/src/hwrenderer/scene/hw_renderstate.h new file mode 100644 index 000000000..22cabc4c6 --- /dev/null +++ b/src/hwrenderer/scene/hw_renderstate.h @@ -0,0 +1,507 @@ +#pragma once + +#include "v_palette.h" +#include "vectors.h" +#include "g_levellocals.h" +#include "hw_drawstructs.h" +#include "hw_drawlist.h" +#include "r_data/matrix.h" +#include "hwrenderer/textures/hw_material.h" + +struct FColormap; +class IVertexBuffer; +class IIndexBuffer; + +enum EClearTarget +{ + CT_Depth = 1, + CT_Stencil = 2, + CT_Color = 4 +}; + +enum ERenderEffect +{ + EFF_NONE = -1, + EFF_FOGBOUNDARY, + EFF_SPHEREMAP, + EFF_BURN, + EFF_STENCIL, + + MAX_EFFECTS +}; + +enum EAlphaFunc +{ + Alpha_GEqual = 0, + Alpha_Greater = 1 +}; + +enum EDrawType +{ + DT_Points = 0, + DT_Lines = 1, + DT_Triangles = 2, + DT_TriangleFan = 3, + DT_TriangleStrip = 4 +}; + +enum EDepthFunc +{ + DF_Less, + DF_LEqual, + DF_Always +}; + +enum EStencilFlags +{ + SF_AllOn = 0, + SF_ColorMaskOff = 1, + SF_DepthMaskOff = 2, + SF_ColorMaskAlpha = 4, // hack value for SSAO +}; + +enum EStencilOp +{ + SOP_Keep = 0, + SOP_Increment = 1, + SOP_Decrement = 2 +}; + +enum ECull +{ + Cull_None, + Cull_CCW, + Cull_CW +}; + + + +struct FStateVec4 +{ + float vec[4]; + + void Set(float r, float g, float b, float a) + { + vec[0] = r; + vec[1] = g; + vec[2] = b; + vec[3] = a; + } +}; + +struct FMaterialState +{ + FMaterial *mMaterial; + int mClampMode; + int mTranslation; + int mOverrideShader; + bool mChanged; + + void Reset() + { + mMaterial = nullptr; + mTranslation = 0; + mClampMode = CLAMP_NONE; + mOverrideShader = -1; + mChanged = false; + } +}; + +struct FDepthBiasState +{ + float mFactor; + float mUnits; + bool mChanged; + + void Reset() + { + mFactor = 0; + mUnits = 0; + mChanged = false; + } +}; + +class FRenderState +{ +protected: + uint8_t mFogEnabled; + uint8_t mTextureEnabled:1; + uint8_t mGlowEnabled : 1; + uint8_t mBrightmapEnabled : 1; + uint8_t mModelMatrixEnabled : 1; + uint8_t mTextureMatrixEnabled : 1; + uint8_t mSplitEnabled : 1; + + int mLightIndex; + int mSpecialEffect; + int mTextureMode; + int mDesaturation; + int mSoftLight; + float mLightParms[4]; + + float mAlphaThreshold; + float mClipSplit[2]; + float mInterpolationFactor; + + FStateVec4 mNormal; + FStateVec4 mColor; + FStateVec4 mGlowTop, mGlowBottom; + FStateVec4 mGlowTopPlane, mGlowBottomPlane; + FStateVec4 mSplitTopPlane, mSplitBottomPlane; + PalEntry mFogColor; + PalEntry mObjectColor; + PalEntry mObjectColor2; + FStateVec4 mDynColor; + FRenderStyle mRenderStyle; + + FMaterialState mMaterial; + FDepthBiasState mBias; + + IVertexBuffer *mVertexBuffer; + int mVertexOffsets[2]; // one per binding point + IIndexBuffer *mIndexBuffer; + + void SetShaderLight(float level, float olight); + +public: + VSMatrix mModelMatrix; + VSMatrix mTextureMatrix; + +public: + + void Reset() + { + mTextureEnabled = true; + mBrightmapEnabled = mFogEnabled = mGlowEnabled = false; + mFogColor.d = -1; + mTextureMode = -1; + mDesaturation = 0; + mAlphaThreshold = 0.5f; + mModelMatrixEnabled = false; + mTextureMatrixEnabled = false; + mSplitEnabled = false; + mObjectColor = 0xffffffff; + mObjectColor2 = 0; + mSoftLight = 0; + mLightParms[0] = mLightParms[1] = mLightParms[2] = 0.0f; + mLightParms[3] = -1.f; + mSpecialEffect = EFF_NONE; + mLightIndex = -1; + mInterpolationFactor = 0; + mRenderStyle = DefaultRenderStyle(); + mMaterial.Reset(); + mBias.Reset(); + + mVertexBuffer = nullptr; + mVertexOffsets[0] = mVertexOffsets[1] = 0; + mIndexBuffer = nullptr; + + mColor.Set(1.0f, 1.0f, 1.0f, 1.0f); + mGlowTop.Set(0.0f, 0.0f, 0.0f, 0.0f); + mGlowBottom.Set(0.0f, 0.0f, 0.0f, 0.0f); + mGlowTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); + mGlowBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); + mSplitTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); + mSplitBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f); + mDynColor.Set(0.0f, 0.0f, 0.0f, 0.0f); + + mModelMatrix.loadIdentity(); + mTextureMatrix.loadIdentity(); + ClearClipSplit(); + } + + void SetNormal(FVector3 norm) + { + mNormal.Set(norm.X, norm.Y, norm.Z, 0.f); + } + + void SetNormal(float x, float y, float z) + { + mNormal.Set(x, y, z, 0.f); + } + + void SetColor(float r, float g, float b, float a = 1.f, int desat = 0) + { + mColor.Set(r, g, b, a); + mDesaturation = desat; + } + + void SetColor(PalEntry pe, int desat = 0) + { + mColor.Set(pe.r / 255.f, pe.g / 255.f, pe.b / 255.f, pe.a / 255.f); + mDesaturation = desat; + } + + void SetColorAlpha(PalEntry pe, float alpha = 1.f, int desat = 0) + { + mColor.Set(pe.r / 255.f, pe.g / 255.f, pe.b / 255.f, alpha); + mDesaturation = desat; + } + + void ResetColor() + { + mColor.Set(1, 1, 1, 1); + mDesaturation = 0; + } + + void SetTextureMode(int mode) + { + mTextureMode = mode; + } + + void SetTextureMode(FRenderStyle style) + { + if (style.Flags & STYLEF_RedIsAlpha) + { + mTextureMode = TM_ALPHATEXTURE; + } + else if (style.Flags & STYLEF_ColorIsFixed) + { + mTextureMode = TM_STENCIL; + } + else if (style.Flags & STYLEF_InvertSource) + { + mTextureMode = TM_INVERSE; + } + } + + int GetTextureMode() + { + return mTextureMode; + } + + void EnableTexture(bool on) + { + mTextureEnabled = on; + } + + void EnableFog(uint8_t on) + { + mFogEnabled = on; + } + + void SetEffect(int eff) + { + mSpecialEffect = eff; + } + + void EnableGlow(bool on) + { + mGlowEnabled = on; + } + + void EnableBrightmap(bool on) + { + mBrightmapEnabled = on; + } + + void EnableSplit(bool on) + { + mSplitEnabled = on; + } + + void EnableModelMatrix(bool on) + { + mModelMatrixEnabled = on; + } + + void EnableTextureMatrix(bool on) + { + mTextureMatrixEnabled = on; + } + + void SetGlowParams(float *t, float *b) + { + mGlowTop.Set(t[0], t[1], t[2], t[3]); + mGlowBottom.Set(b[0], b[1], b[2], b[3]); + } + + void SetSoftLightLevel(int llevel, int blendfactor = 0) + { + if (level.lightmode == 8 && blendfactor == 0) mLightParms[3] = llevel / 255.f; + else mLightParms[3] = -1.f; + } + + void SetGlowPlanes(const secplane_t &top, const secplane_t &bottom) + { + DVector3 tn = top.Normal(); + DVector3 bn = bottom.Normal(); + mGlowTopPlane.Set((float)tn.X, (float)tn.Y, (float)(1. / tn.Z), (float)top.fD()); + mGlowBottomPlane.Set((float)bn.X, (float)bn.Y, (float)(1. / bn.Z), (float)bottom.fD()); + } + + void SetSplitPlanes(const secplane_t &top, const secplane_t &bottom) + { + DVector3 tn = top.Normal(); + DVector3 bn = bottom.Normal(); + mSplitTopPlane.Set((float)tn.X, (float)tn.Y, (float)(1. / tn.Z), (float)top.fD()); + mSplitBottomPlane.Set((float)bn.X, (float)bn.Y, (float)(1. / bn.Z), (float)bottom.fD()); + } + + void SetDynLight(float r, float g, float b) + { + mDynColor.Set(r, g, b, 0); + } + + void SetObjectColor(PalEntry pe) + { + mObjectColor = pe; + } + + void SetObjectColor2(PalEntry pe) + { + mObjectColor2 = pe; + } + + void SetFog(PalEntry c, float d) + { + const float LOG2E = 1.442692f; // = 1/log(2) + mFogColor = c; + if (d >= 0.0f) mLightParms[2] = d * (-LOG2E / 64000.f); + } + + void SetLightParms(float f, float d) + { + mLightParms[1] = f; + mLightParms[0] = d; + } + + PalEntry GetFogColor() const + { + return mFogColor; + } + + void AlphaFunc(int func, float thresh) + { + if (func == Alpha_Greater) mAlphaThreshold = thresh; + else mAlphaThreshold = thresh - 0.001f; + } + + void SetPlaneTextureRotation(GLSectorPlane *plane, FMaterial *texture) + { + if (hw_SetPlaneTextureRotation(plane, texture, mTextureMatrix)) + { + EnableTextureMatrix(true); + } + } + + void SetLightIndex(int index) + { + mLightIndex = index; + } + + void SetRenderStyle(FRenderStyle rs) + { + mRenderStyle = rs; + } + + void SetRenderStyle(ERenderStyle rs) + { + mRenderStyle = rs; + } + + void SetDepthBias(float a, float b) + { + mBias.mFactor = a; + mBias.mUnits = b; + mBias.mChanged = true; + } + + void ClearDepthBias() + { + mBias.mFactor = 0; + mBias.mUnits = 0; + mBias.mChanged = true; + } + + void SetMaterial(FMaterial *mat, int clampmode, int translation, int overrideshader) + { + mMaterial.mMaterial = mat; + mMaterial.mClampMode = clampmode; + mMaterial.mTranslation = translation; + mMaterial.mOverrideShader = overrideshader; + mMaterial.mChanged = true; + } + + void SetClipSplit(float bottom, float top) + { + mClipSplit[0] = bottom; + mClipSplit[1] = top; + } + + void SetClipSplit(float *vals) + { + memcpy(mClipSplit, vals, 2 * sizeof(float)); + } + + void GetClipSplit(float *out) + { + memcpy(out, mClipSplit, 2 * sizeof(float)); + } + + void ClearClipSplit() + { + mClipSplit[0] = -1000000.f; + mClipSplit[1] = 1000000.f; + } + + void SetVertexBuffer(IVertexBuffer *vb, int offset0, int offset1) + { + assert(vb); + mVertexBuffer = vb; + mVertexOffsets[0] = offset0; + mVertexOffsets[1] = offset1; + } + + void SetIndexBuffer(IIndexBuffer *ib) + { + mIndexBuffer = ib; + } + + template void SetVertexBuffer(T *buffer) + { + auto ptrs = buffer->GetBufferObjects(); + SetVertexBuffer(ptrs.first, 0, 0); + SetIndexBuffer(ptrs.second); + } + + void SetInterpolationFactor(float fac) + { + mInterpolationFactor = fac; + } + + float GetInterpolationFactor() + { + return mInterpolationFactor; + } + + void SetColor(int sectorlightlevel, int rellight, bool fullbright, const FColormap &cm, float alpha, bool weapon = false); + void SetFog(int lightlevel, int rellight, bool fullbright, const FColormap *cmap, bool isadditive); + + // API-dependent render interface + + // Draw commands + virtual void ClearScreen() = 0; + virtual void Draw(int dt, int index, int count, bool apply = true) = 0; + virtual void DrawIndexed(int dt, int index, int count, bool apply = true) = 0; + + // Immediate render state change commands. These only change infrequently and should not clutter the render state. + virtual bool SetDepthClamp(bool on) = 0; // Deactivated only by skyboxes. + virtual void SetDepthMask(bool on) = 0; // Used by decals and indirectly by portal setup. + virtual void SetDepthFunc(int func) = 0; // Used by models, portals and mirror surfaces. + virtual void SetDepthRange(float min, float max) = 0; // Used by portal setup. + virtual void EnableDrawBufferAttachments(bool on) = 0; // Used by fog boundary drawer. + virtual void SetStencil(int offs, int op, int flags) = 0; // Used by portal setup and render hacks. + virtual void SetCulling(int mode) = 0; // Used by model drawer only. + virtual void EnableClipDistance(int num, bool state) = 0; // Use by sprite sorter for vertical splits. + virtual void Clear(int targets) = 0; // not used during normal rendering + virtual void EnableStencil(bool on) = 0; // always on for 3D, always off for 2D + virtual void SetScissor(int x, int y, int w, int h) = 0; // constant for 3D, changes for 2D + virtual void SetViewport(int x, int y, int w, int h) = 0; // constant for all 3D and all 2D + virtual void EnableDepthTest(bool on) = 0; // used by 2D, portals and render hacks. + virtual void EnableMultisampling(bool on) = 0; // only active for 2D + virtual void EnableLineSmooth(bool on) = 0; // constant setting for each 2D drawer operation + + +}; + diff --git a/src/hwrenderer/scene/hw_skydome.cpp b/src/hwrenderer/scene/hw_skydome.cpp index de8a51af1..7184b7bad 100644 --- a/src/hwrenderer/scene/hw_skydome.cpp +++ b/src/hwrenderer/scene/hw_skydome.cpp @@ -23,7 +23,7 @@ ** ** Draws the sky. Loosely based on the JDoom sky and the ZDoomGL 0.66.2 sky. ** -** for FSkyDomeCreator::SkyVertex only: +** for FSkyVertexBuffer::SkyVertex only: **--------------------------------------------------------------------------- ** Copyright 2003 Tim Stump ** All rights reserved. @@ -60,10 +60,13 @@ #include "r_utility.h" #include "g_levellocals.h" #include "r_sky.h" +#include "cmdlib.h" #include "textures/skyboxtexture.h" #include "hwrenderer/textures/hw_material.h" #include "hw_skydome.h" +#include "hw_renderstate.h" +#include "hwrenderer/data/buffers.h" //----------------------------------------------------------------------------- // @@ -79,13 +82,18 @@ EXTERN_CVAR(Float, skyoffset) // //----------------------------------------------------------------------------- -FSkyDomeCreator::FSkyDomeCreator() +FSkyVertexBuffer::FSkyVertexBuffer() { CreateDome(); -} + mVertexBuffer = screen->CreateVertexBuffer(); -FSkyDomeCreator::~FSkyDomeCreator() -{ + static const FVertexBufferAttribute format[] = { + { 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(FSkyVertex, x) }, + { 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FSkyVertex, u) }, + { 0, VATTR_COLOR, VFmt_Byte4, (int)myoffsetof(FSkyVertex, color) } + }; + mVertexBuffer->SetFormat(1, 3, sizeof(FSkyVertex), format); + mVertexBuffer->SetData(mVertices.Size() * sizeof(FSkyVertex), &mVertices[0], true); } //----------------------------------------------------------------------------- @@ -94,7 +102,7 @@ FSkyDomeCreator::~FSkyDomeCreator() // //----------------------------------------------------------------------------- -void FSkyDomeCreator::SkyVertex(int r, int c, bool zflip) +void FSkyVertexBuffer::SkyVertex(int r, int c, bool zflip) { static const FAngle maxSideAngle = 60.f; static const float scale = 10000.; @@ -138,7 +146,7 @@ void FSkyDomeCreator::SkyVertex(int r, int c, bool zflip) // //----------------------------------------------------------------------------- -void FSkyDomeCreator::CreateSkyHemisphere(int hemi) +void FSkyVertexBuffer::CreateSkyHemisphere(int hemi) { int r, c; bool zflip = !!(hemi & SKYHEMI_LOWER); @@ -169,7 +177,7 @@ void FSkyDomeCreator::CreateSkyHemisphere(int hemi) // //----------------------------------------------------------------------------- -void FSkyDomeCreator::CreateDome() +void FSkyVertexBuffer::CreateDome() { // the first thing we put into the buffer is the fog layer object which is just 4 triangles around the viewpoint. @@ -268,7 +276,7 @@ void FSkyDomeCreator::CreateDome() // //----------------------------------------------------------------------------- -void FSkyDomeCreator::SetupMatrices(FMaterial *tex, float x_offset, float y_offset, bool mirror, int mode, VSMatrix &modelMatrix, VSMatrix &textureMatrix) +void FSkyVertexBuffer::SetupMatrices(FMaterial *tex, float x_offset, float y_offset, bool mirror, int mode, VSMatrix &modelMatrix, VSMatrix &textureMatrix) { int texw = tex->TextureWidth(); int texh = tex->TextureHeight(); @@ -310,4 +318,4 @@ void FSkyDomeCreator::SetupMatrices(FMaterial *tex, float x_offset, float y_offs textureMatrix.loadIdentity(); textureMatrix.scale(mirror ? -xscale : xscale, yscale, 1.f); textureMatrix.translate(1.f, y_offset / texh, 1.f); -} \ No newline at end of file +} diff --git a/src/hwrenderer/scene/hw_skydome.h b/src/hwrenderer/scene/hw_skydome.h index 9fcaf29ef..b441bc8ca 100644 --- a/src/hwrenderer/scene/hw_skydome.h +++ b/src/hwrenderer/scene/hw_skydome.h @@ -4,6 +4,8 @@ #include "r_data/matrix.h" class FMaterial; +class FRenderState; +class IVertexBuffer; struct FSkyVertex { @@ -32,8 +34,10 @@ struct FSkyVertex }; -class FSkyDomeCreator +struct HWSkyPortal; +class FSkyVertexBuffer { + friend struct HWSkyPortal; public: static const int SKYHEMI_UPPER = 1; static const int SKYHEMI_LOWER = 2; @@ -45,7 +49,8 @@ public: SKYMODE_FOGLAYER = 2 }; -protected: + IVertexBuffer *mVertexBuffer; + TArray mVertices; TArray mPrimStart; @@ -61,9 +66,12 @@ protected: public: - FSkyDomeCreator(); - virtual ~FSkyDomeCreator(); + FSkyVertexBuffer(); void SetupMatrices(FMaterial *tex, float x_offset, float y_offset, bool mirror, int mode, VSMatrix &modelmatrix, VSMatrix &textureMatrix); + std::pair GetBufferObjects() const + { + return std::make_pair(mVertexBuffer, nullptr); + } int FaceStart(int i) { diff --git a/src/hwrenderer/scene/hw_skyportal.cpp b/src/hwrenderer/scene/hw_skyportal.cpp new file mode 100644 index 000000000..9da564e63 --- /dev/null +++ b/src/hwrenderer/scene/hw_skyportal.cpp @@ -0,0 +1,222 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2003-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "doomtype.h" +#include "g_level.h" +#include "w_wad.h" +#include "r_state.h" +#include "r_utility.h" +#include "g_levellocals.h" +#include "hwrenderer/scene/hw_skydome.h" +#include "hwrenderer/scene/hw_portal.h" +#include "hwrenderer/scene/hw_renderstate.h" +#include "textures/skyboxtexture.h" + + + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +void HWSkyPortal::RenderRow(HWDrawInfo *di, FRenderState &state, EDrawType prim, int row, bool apply) +{ + state.Draw(prim, vertexBuffer->mPrimStart[row], vertexBuffer->mPrimStart[row + 1] - vertexBuffer->mPrimStart[row]); +} + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +void HWSkyPortal::RenderDome(HWDrawInfo *di, FRenderState &state, FMaterial * tex, float x_offset, float y_offset, bool mirror, int mode) +{ + if (tex) + { + state.SetMaterial(tex, CLAMP_NONE, 0, -1); + state.EnableModelMatrix(true); + state.EnableTextureMatrix(true); + + vertexBuffer->SetupMatrices(tex, x_offset, y_offset, mirror, mode, state.mModelMatrix, state.mTextureMatrix); + } + + int rc = vertexBuffer->mRows + 1; + + // The caps only get drawn for the main layer but not for the overlay. + if (mode == FSkyVertexBuffer::SKYMODE_MAINLAYER && tex != NULL) + { + PalEntry pe = tex->tex->GetSkyCapColor(false); + state.SetObjectColor(pe); + state.EnableTexture(false); + RenderRow(di, state, DT_TriangleFan, 0); + + pe = tex->tex->GetSkyCapColor(true); + state.SetObjectColor(pe); + RenderRow(di, state, DT_TriangleFan, rc); + state.EnableTexture(true); + } + state.SetObjectColor(0xffffffff); + for (int i = 1; i <= vertexBuffer->mRows; i++) + { + RenderRow(di, state, DT_TriangleStrip, i, i == 1); + RenderRow(di, state, DT_TriangleStrip, rc + i, false); + } + + state.EnableTextureMatrix(false); + state.EnableModelMatrix(false); +} + + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +void HWSkyPortal::RenderBox(HWDrawInfo *di, FRenderState &state, FTextureID texno, FMaterial * gltex, float x_offset, bool sky2) +{ + FSkyBox * sb = static_cast(gltex->tex); + int faces; + FMaterial * tex; + + state.EnableModelMatrix(true); + state.mModelMatrix.loadIdentity(); + + if (!sky2) + state.mModelMatrix.rotate(-180.0f+x_offset, level.info->skyrotatevector.X, level.info->skyrotatevector.Z, level.info->skyrotatevector.Y); + else + state.mModelMatrix.rotate(-180.0f+x_offset, level.info->skyrotatevector2.X, level.info->skyrotatevector2.Z, level.info->skyrotatevector2.Y); + + if (sb->faces[5]) + { + faces=4; + + // north + tex = FMaterial::ValidateTexture(sb->faces[0], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(0), 4); + + // east + tex = FMaterial::ValidateTexture(sb->faces[1], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(1), 4); + + // south + tex = FMaterial::ValidateTexture(sb->faces[2], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(2), 4); + + // west + tex = FMaterial::ValidateTexture(sb->faces[3], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(3), 4); + } + else + { + faces=1; + tex = FMaterial::ValidateTexture(sb->faces[0], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(-1), 10); + } + + // top + tex = FMaterial::ValidateTexture(sb->faces[faces], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(sb->fliptop ? 6 : 5), 4); + + // bottom + tex = FMaterial::ValidateTexture(sb->faces[faces+1], false); + state.SetMaterial(tex, CLAMP_XY, 0, -1); + state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(4), 4); + + state.EnableModelMatrix(false); +} + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- +void HWSkyPortal::DrawContents(HWDrawInfo *di, FRenderState &state) +{ + bool drawBoth = false; + auto &vp = di->Viewpoint; + + // We have no use for Doom lighting special handling here, so disable it for this function. + int oldlightmode = ::level.lightmode; + if (::level.lightmode == 8) + { + ::level.lightmode = 2; + state.SetSoftLightLevel(-1); + } + + + state.ResetColor(); + state.EnableFog(false); + state.AlphaFunc(Alpha_GEqual, 0.f); + state.SetRenderStyle(STYLE_Translucent); + bool oldClamp = state.SetDepthClamp(true); + + di->SetupView(state, 0, 0, 0, !!(mState->MirrorFlag & 1), !!(mState->PlaneMirrorFlag & 1)); + + state.SetVertexBuffer(vertexBuffer); + if (origin->texture[0] && origin->texture[0]->tex->bSkybox) + { + RenderBox(di, state, origin->skytexno1, origin->texture[0], origin->x_offset[0], origin->sky2); + } + else + { + if (origin->texture[0]==origin->texture[1] && origin->doublesky) origin->doublesky=false; + + if (origin->texture[0]) + { + state.SetTextureMode(TM_OPAQUE); + RenderDome(di, state, origin->texture[0], origin->x_offset[0], origin->y_offset, origin->mirrored, FSkyVertexBuffer::SKYMODE_MAINLAYER); + state.SetTextureMode(TM_NORMAL); + } + + state.AlphaFunc(Alpha_Greater, 0.f); + + if (origin->doublesky && origin->texture[1]) + { + RenderDome(di, state, origin->texture[1], origin->x_offset[1], origin->y_offset, false, FSkyVertexBuffer::SKYMODE_SECONDLAYER); + } + + if (::level.skyfog>0 && !di->isFullbrightScene() && (origin->fadecolor & 0xffffff) != 0) + { + PalEntry FadeColor = origin->fadecolor; + FadeColor.a = clamp(::level.skyfog, 0, 255); + + state.EnableTexture(false); + state.SetObjectColor(FadeColor); + state.Draw(DT_Triangles, 0, 12); + state.EnableTexture(true); + state.SetObjectColor(0xffffffff); + } + } + ::level.lightmode = oldlightmode; + state.SetDepthClamp(oldClamp); +} + +const char *HWSkyPortal::GetName() { return "Sky"; } diff --git a/src/hwrenderer/scene/hw_spritelight.cpp b/src/hwrenderer/scene/hw_spritelight.cpp index 6b55f67ce..9ce11f859 100644 --- a/src/hwrenderer/scene/hw_spritelight.cpp +++ b/src/hwrenderer/scene/hw_spritelight.cpp @@ -103,7 +103,7 @@ void HWDrawInfo::GetDynSpriteLight(AActor *self, float x, float y, float z, FLig frac *= (float)smoothstep(light->SpotOuterAngle.Cos(), light->SpotInnerAngle.Cos(), cosDir); } - if (frac > 0 && (!light->shadowmapped || mShadowMap->ShadowTest(light, { x, y, z }))) + if (frac > 0 && (!light->shadowmapped || screen->mShadowMap.ShadowTest(light, { x, y, z }))) { lr = light->GetRed() / 255.0f; lg = light->GetGreen() / 255.0f; diff --git a/src/hwrenderer/scene/hw_sprites.cpp b/src/hwrenderer/scene/hw_sprites.cpp index abdc50853..4bda3ab71 100644 --- a/src/hwrenderer/scene/hw_sprites.cpp +++ b/src/hwrenderer/scene/hw_sprites.cpp @@ -42,15 +42,19 @@ #include "r_data/models/models.h" #include "vectors.h" +#include "hwrenderer/models/hw_models.h" #include "hwrenderer/scene/hw_drawstructs.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "hwrenderer/scene/hw_fakeflat.h" #include "hwrenderer/scene/hw_portal.h" +#include "hwrenderer/data/flatvertices.h" #include "hwrenderer/utility/hw_cvars.h" #include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/utility/hw_lighting.h" #include "hwrenderer/textures/hw_material.h" #include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hw_renderstate.h" extern TArray sprites; extern TArray SpriteFrames; @@ -62,6 +66,238 @@ EXTERN_CVAR(Bool, r_debug_disable_vis_filter) EXTERN_CVAR(Float, transsouls) +//========================================================================== +// +// +// +//========================================================================== + +void GLSprite::DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent) +{ + bool additivefog = false; + bool foglayer = false; + int rel = fullbright ? 0 : getExtraLight(); + auto &vp = di->Viewpoint; + + if (translucent) + { + // The translucent pass requires special setup for the various modes. + + // for special render styles brightmaps would not look good - especially for subtractive. + if (RenderStyle.BlendOp != STYLEOP_Add) + { + state.EnableBrightmap(false); + } + + // Optionally use STYLE_ColorBlend in place of STYLE_Add for fullbright items. + if (RenderStyle == LegacyRenderStyles[STYLE_Add] && trans > 1.f - FLT_EPSILON && + gl_usecolorblending && !di->isFullbrightScene() && actor && + fullbright && gltexture && !gltexture->tex->GetTranslucency()) + { + RenderStyle = LegacyRenderStyles[STYLE_ColorBlend]; + } + + state.SetRenderStyle(RenderStyle); + state.SetTextureMode(RenderStyle); + + if (hw_styleflags == STYLEHW_NoAlphaTest) + { + state.AlphaFunc(Alpha_GEqual, 0.f); + } + else if (!gltexture || !gltexture->tex->GetTranslucency()) state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); + else state.AlphaFunc(Alpha_Greater, 0.f); + + if (RenderStyle.BlendOp == STYLEOP_Shadow) + { + float fuzzalpha = 0.44f; + float minalpha = 0.1f; + + // fog + fuzz don't work well without some fiddling with the alpha value! + if (!Colormap.FadeColor.isBlack()) + { + float dist = Dist2(vp.Pos.X, vp.Pos.Y, x, y); + int fogd = hw_GetFogDensity(lightlevel, Colormap.FadeColor, Colormap.FogDensity, Colormap.BlendFactor); + + // this value was determined by trial and error and is scale dependent! + float factor = 0.05f + exp(-fogd * dist / 62500.f); + fuzzalpha *= factor; + minalpha *= factor; + } + + state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); + state.SetColor(0.2f, 0.2f, 0.2f, fuzzalpha, Colormap.Desaturation); + additivefog = true; + lightlist = nullptr; // the fuzz effect does not use the sector's light level so splitting is not needed. + } + else if (RenderStyle.BlendOp == STYLEOP_Add && RenderStyle.DestAlpha == STYLEALPHA_One) + { + additivefog = true; + } + } + else if (modelframe == nullptr) + { + // This still needs to set the texture mode. As blend mode it will always use GL_ONE/GL_ZERO + state.SetTextureMode(RenderStyle); + state.SetDepthBias(-1, -128); + } + if (RenderStyle.BlendOp != STYLEOP_Shadow) + { + if (level.HasDynamicLights && !di->isFullbrightScene() && !fullbright) + { + if (dynlightindex == -1) // only set if we got no light buffer index. This covers all cases where sprite lighting is used. + { + float out[3]; + di->GetDynSpriteLight(gl_light_sprites ? actor : nullptr, gl_light_particles ? particle : nullptr, out); + state.SetDynLight(out[0], out[1], out[2]); + } + } + sector_t *cursec = actor ? actor->Sector : particle ? particle->subsector->sector : nullptr; + if (cursec != nullptr) + { + const PalEntry finalcol = fullbright + ? ThingColor + : ThingColor.Modulate(cursec->SpecialColors[sector_t::sprites]); + + state.SetObjectColor(finalcol); + } + state.SetColor(lightlevel, rel, di->isFullbrightScene(), Colormap, trans); + } + + + if (Colormap.FadeColor.isBlack()) foglevel = lightlevel; + + if (RenderStyle.Flags & STYLEF_FadeToBlack) + { + Colormap.FadeColor = 0; + additivefog = true; + } + + if (RenderStyle.BlendOp == STYLEOP_RevSub || RenderStyle.BlendOp == STYLEOP_Sub) + { + if (!modelframe) + { + // non-black fog with subtractive style needs special treatment + if (!Colormap.FadeColor.isBlack()) + { + foglayer = true; + // Due to the two-layer approach we need to force an alpha test that lets everything pass + state.AlphaFunc(Alpha_Greater, 0); + } + } + else RenderStyle.BlendOp = STYLEOP_Fuzz; // subtractive with models is not going to work. + } + + if (!foglayer) state.SetFog(foglevel, rel, di->isFullbrightScene(), &Colormap, additivefog); + else + { + state.EnableFog(false); + state.SetFog(0, 0); + } + + if (gltexture) state.SetMaterial(gltexture, CLAMP_XY, translation, OverrideShader); + else if (!modelframe) state.EnableTexture(false); + + //SetColor(lightlevel, rel, Colormap, trans); + + unsigned int iter = lightlist ? lightlist->Size() : 1; + bool clipping = false; + if (lightlist || topclip != LARGE_VALUE || bottomclip != -LARGE_VALUE) + { + clipping = true; + state.EnableSplit(true); + } + + secplane_t bottomp = { { 0, 0, -1. }, bottomclip }; + secplane_t topp = { { 0, 0, -1. }, topclip }; + for (unsigned i = 0; i < iter; i++) + { + if (lightlist) + { + // set up the light slice + secplane_t *topplane = i == 0 ? &topp : &(*lightlist)[i].plane; + secplane_t *lowplane = i == (*lightlist).Size() - 1 ? &bottomp : &(*lightlist)[i + 1].plane; + + int thislight = (*lightlist)[i].caster != nullptr ? hw_ClampLight(*(*lightlist)[i].p_lightlevel) : lightlevel; + int thisll = actor == nullptr ? thislight : (uint8_t)actor->Sector->CheckSpriteGlow(thislight, actor->InterpolatedPosition(vp.TicFrac)); + + FColormap thiscm; + thiscm.CopyFog(Colormap); + thiscm.CopyFrom3DLight(&(*lightlist)[i]); + if (level.flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) + { + thiscm.Decolorize(); + } + + state.SetColor(thisll, rel, di->isFullbrightScene(), thiscm, trans); + if (!foglayer) + { + state.SetFog(thislight, rel, di->isFullbrightScene(), &thiscm, additivefog); + } + state.SetSplitPlanes(*topplane, *lowplane); + } + else if (clipping) + { + state.SetSplitPlanes(topp, bottomp); + } + + if (!modelframe) + { + state.SetNormal(0, 0, 0); + + if (screen->BuffersArePersistent()) + { + CreateVertices(di); + } + if (polyoffset) + { + state.SetDepthBias(-1, -128); + } + state.SetLightIndex(-1); + state.Draw(DT_TriangleStrip, vertexindex, 4); + + if (foglayer) + { + // If we get here we know that we have colored fog and no fixed colormap. + state.SetFog(foglevel, rel, false, &Colormap, additivefog); + state.SetTextureMode(TM_FOGLAYER); + state.SetRenderStyle(STYLE_Translucent); + state.Draw(DT_TriangleStrip, vertexindex, 4); + state.SetTextureMode(TM_NORMAL); + } + } + else + { + FGLModelRenderer renderer(di, state, dynlightindex); + renderer.RenderModel(x, y, z, modelframe, actor, di->Viewpoint.TicFrac); + state.SetVertexBuffer(screen->mVertexData); + } + } + + if (clipping) + { + state.EnableSplit(false); + } + + if (translucent) + { + state.EnableBrightmap(true); + state.SetRenderStyle(STYLE_Translucent); + state.SetTextureMode(TM_NORMAL); + if (actor != nullptr && (actor->renderflags & RF_SPRITETYPEMASK) == RF_FLATSPRITE) + { + state.ClearDepthBias(); + } + } + else if (modelframe == nullptr) + { + state.ClearDepthBias(); + } + + state.SetObjectColor(0xffffffff); + state.EnableTexture(true); + state.SetDynLight(0, 0, 0); +} + //========================================================================== // // @@ -220,18 +456,47 @@ bool GLSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) inline void GLSprite::PutSprite(HWDrawInfo *di, bool translucent) { // That's a lot of checks... - if (modelframe && RenderStyle.BlendOp != STYLEOP_Shadow && gl_light_sprites && level.HasDynamicLights && !di->isFullbrightScene() && !fullbright) + if (modelframe && !modelframe->isVoxel && RenderStyle.BlendOp != STYLEOP_Shadow && gl_light_sprites && level.HasDynamicLights && !di->isFullbrightScene() && !fullbright) { hw_GetDynModelLight(actor, lightdata); - dynlightindex = di->UploadLights(lightdata); + dynlightindex = screen->mLights->UploadLights(lightdata); } else dynlightindex = -1; - + vertexindex = -1; + if (!screen->BuffersArePersistent()) + { + CreateVertices(di); + } di->AddSprite(this, translucent); } +//========================================================================== +// +// +// +//========================================================================== + +void GLSprite::CreateVertices(HWDrawInfo *di) +{ + if (modelframe == nullptr) + { + FVector3 v[4]; + polyoffset = CalculateVertices(di, v, &di->Viewpoint.Pos); + auto vert = screen->mVertexData->AllocVertices(4); + auto vp = vert.first; + vertexindex = vert.second; + + vp[0].Set(v[0][0], v[0][1], v[0][2], ul, vt); + vp[1].Set(v[1][0], v[1][1], v[1][2], ur, vt); + vp[2].Set(v[2][0], v[2][1], v[2][2], ul, vb); + vp[3].Set(v[3][0], v[3][1], v[3][2], ur, vb); + } + +} + + //========================================================================== // // diff --git a/src/hwrenderer/scene/hw_walls.cpp b/src/hwrenderer/scene/hw_walls.cpp index d164aaf31..8c5e90d87 100644 --- a/src/hwrenderer/scene/hw_walls.cpp +++ b/src/hwrenderer/scene/hw_walls.cpp @@ -38,7 +38,229 @@ #include "hwrenderer/scene/hw_drawinfo.h" #include "hwrenderer/scene/hw_drawstructs.h" #include "hwrenderer/scene/hw_portal.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hw_renderstate.h" +#include "hw_skydome.h" +//========================================================================== +// +// General purpose wall rendering function +// everything goes through here +// +//========================================================================== + +void GLWall::RenderWall(HWDrawInfo *di, FRenderState &state, int textured) +{ + assert(vertcount > 0); + state.SetLightIndex(dynlightindex); + state.Draw(DT_TriangleFan, vertindex, vertcount); + vertexcount += vertcount; +} + +//========================================================================== +// +// +// +//========================================================================== + +void GLWall::RenderFogBoundary(HWDrawInfo *di, FRenderState &state) +{ + if (gl_fogmode && !di->isFullbrightScene()) + { + int rel = rellight + getExtraLight(); + state.EnableDrawBufferAttachments(false); + state.SetFog(lightlevel, rel, false, &Colormap, false); + state.SetEffect(EFF_FOGBOUNDARY); + state.AlphaFunc(Alpha_GEqual, 0.f); + state.SetDepthBias(-1, -128); + RenderWall(di, state, GLWall::RWF_BLANK); + state.ClearDepthBias(); + state.SetEffect(EFF_NONE); + state.EnableDrawBufferAttachments(true); + } +} + + +//========================================================================== +// +// +// +//========================================================================== +void GLWall::RenderMirrorSurface(HWDrawInfo *di, FRenderState &state) +{ + if (!TexMan.mirrorTexture.isValid()) return; + + state.SetDepthFunc(DF_LEqual); + + // we use texture coordinates and texture matrix to pass the normal stuff to the shader so that the default vertex buffer format can be used as is. + state.EnableTextureMatrix(true); + + // Use sphere mapping for this + state.SetEffect(EFF_SPHEREMAP); + state.SetColor(lightlevel, 0, di->isFullbrightScene(), Colormap, 0.1f); + state.SetFog(lightlevel, 0, di->isFullbrightScene(), &Colormap, true); + state.SetRenderStyle(STYLE_Add); + state.AlphaFunc(Alpha_Greater, 0); + + FMaterial * pat = FMaterial::ValidateTexture(TexMan.mirrorTexture, false, false); + state.SetMaterial(pat, CLAMP_NONE, 0, -1); + + flags &= ~GLWall::GLWF_GLOW; + RenderWall(di, state, GLWall::RWF_BLANK); + + state.EnableTextureMatrix(false); + state.SetEffect(EFF_NONE); + state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); + + state.SetDepthFunc(DF_Less); + + // This is drawn in the translucent pass which is done after the decal pass + // As a result the decals have to be drawn here, right after the wall they are on, + // because the depth buffer won't get set by translucent items. + if (seg->sidedef->AttachedDecals) + { + DrawDecalsForMirror(di, state, di->Decals[1]); + } + state.SetRenderStyle(STYLE_Translucent); +} + +//========================================================================== +// +// +// +//========================================================================== + +void GLWall::RenderTexturedWall(HWDrawInfo *di, FRenderState &state, int rflags) +{ + int tmode = state.GetTextureMode(); + int rel = rellight + getExtraLight(); + + if (flags & GLWall::GLWF_GLOW) + { + state.EnableGlow(true); + state.SetGlowParams(topglowcolor, bottomglowcolor); + } + state.SetGlowPlanes(topplane, bottomplane); + state.SetMaterial(gltexture, flags & 3, 0, -1); + + if (type == RENDERWALL_M2SNF) + { + if (flags & GLWall::GLWF_CLAMPY) + { + if (tmode == TM_NORMAL) state.SetTextureMode(TM_CLAMPY); + } + state.SetFog(255, 0, di->isFullbrightScene(), nullptr, false); + } + state.SetObjectColor(seg->frontsector->SpecialColors[sector_t::walltop] | 0xff000000); + state.SetObjectColor2(seg->frontsector->SpecialColors[sector_t::wallbottom] | 0xff000000); + + float absalpha = fabsf(alpha); + if (lightlist == nullptr) + { + if (type != RENDERWALL_M2SNF) state.SetFog(lightlevel, rel, di->isFullbrightScene(), &Colormap, RenderStyle == STYLE_Add); + state.SetColor(lightlevel, rel, di->isFullbrightScene(), Colormap, absalpha); + RenderWall(di, state, rflags); + } + else + { + state.EnableSplit(true); + + for (unsigned i = 0; i < lightlist->Size(); i++) + { + secplane_t &lowplane = i == (*lightlist).Size() - 1 ? bottomplane : (*lightlist)[i + 1].plane; + // this must use the exact same calculation method as GLWall::Process etc. + float low1 = lowplane.ZatPoint(vertexes[0]); + float low2 = lowplane.ZatPoint(vertexes[1]); + + if (low1 < ztop[0] || low2 < ztop[1]) + { + int thisll = (*lightlist)[i].caster != NULL ? hw_ClampLight(*(*lightlist)[i].p_lightlevel) : lightlevel; + FColormap thiscm; + thiscm.FadeColor = Colormap.FadeColor; + thiscm.FogDensity = Colormap.FogDensity; + thiscm.CopyFrom3DLight(&(*lightlist)[i]); + state.SetColor(thisll, rel, false, thiscm, absalpha); + if (type != RENDERWALL_M2SNF) state.SetFog(thisll, rel, false, &thiscm, RenderStyle == STYLE_Add); + state.SetSplitPlanes((*lightlist)[i].plane, lowplane); + RenderWall(di, state, rflags); + } + if (low1 <= zbottom[0] && low2 <= zbottom[1]) break; + } + + state.EnableSplit(false); + } + state.SetObjectColor(0xffffffff); + state.SetObjectColor2(0); + state.SetTextureMode(tmode); + state.EnableGlow(false); +} + +//========================================================================== +// +// +// +//========================================================================== + +void GLWall::RenderTranslucentWall(HWDrawInfo *di, FRenderState &state) +{ + state.SetRenderStyle(RenderStyle); + if (gltexture) + { + if (!gltexture->tex->GetTranslucency()) state.AlphaFunc(Alpha_GEqual, gl_mask_threshold); + else state.AlphaFunc(Alpha_GEqual, 0.f); + RenderTexturedWall(di, state, GLWall::RWF_TEXTURED | GLWall::RWF_NOSPLIT); + } + else + { + state.AlphaFunc(Alpha_GEqual, 0.f); + state.SetColor(lightlevel, 0, false, Colormap, fabsf(alpha)); + state.SetFog(lightlevel, 0, false, &Colormap, RenderStyle == STYLE_Add); + state.EnableTexture(false); + RenderWall(di, state, GLWall::RWF_NOSPLIT); + state.EnableTexture(true); + } + state.SetRenderStyle(STYLE_Translucent); +} + +//========================================================================== +// +// +// +//========================================================================== +void GLWall::DrawWall(HWDrawInfo *di, FRenderState &state, bool translucent) +{ + if (screen->BuffersArePersistent()) + { + if (level.HasDynamicLights && !di->isFullbrightScene() && gltexture != nullptr) + { + SetupLights(di, lightdata); + } + MakeVertices(di, !!(flags & GLWall::GLWF_TRANSLUCENT)); + } + + state.SetNormal(glseg.Normal()); + if (!translucent) + { + RenderTexturedWall(di, state, GLWall::RWF_TEXTURED); + } + else + { + switch (type) + { + case RENDERWALL_MIRRORSURFACE: + RenderMirrorSurface(di, state); + break; + + case RENDERWALL_FOGBOUNDARY: + RenderFogBoundary(di, state); + break; + + default: + RenderTranslucentWall(di, state); + break; + } + } +} //========================================================================== // @@ -132,13 +354,13 @@ void GLWall::SetupLights(HWDrawInfo *di, FDynLightData &lightdata) } if (outcnt[0]!=4 && outcnt[1]!=4 && outcnt[2]!=4 && outcnt[3]!=4) { - lightdata.GetLight(seg->frontsector->PortalGroup, p, node->lightsource, true); + draw_dlight += lightdata.GetLight(seg->frontsector->PortalGroup, p, node->lightsource, true); } } } node = node->nextLight; } - dynlightindex = di->UploadLights(lightdata); + dynlightindex = screen->mLights->UploadLights(lightdata); } @@ -182,7 +404,7 @@ void GLWall::PutWall(HWDrawInfo *di, bool translucent) if (di->isFullbrightScene() || (Colormap.LightColor.isWhite() && lightlevel == 255)) flags &= ~GLWF_GLOW; - if (!(screen->hwcaps & RFL_BUFFER_STORAGE)) + if (!screen->BuffersArePersistent()) { if (level.HasDynamicLights && !di->isFullbrightScene() && gltexture != nullptr) { @@ -207,9 +429,117 @@ void GLWall::PutWall(HWDrawInfo *di, bool translucent) flags &= ~GLWF_TRANSLUCENT; } +//========================================================================== +// +// +// +//========================================================================== + void GLWall::PutPortal(HWDrawInfo *di, int ptype) { - di->AddPortal(this, ptype); + auto pstate = screen->mPortalState; + HWPortal * portal; + + MakeVertices(di, false); + switch (ptype) + { + // portals don't go into the draw list. + // Instead they are added to the portal manager + case PORTALTYPE_HORIZON: + horizon = pstate->UniqueHorizons.Get(horizon); + portal = di->FindPortal(horizon); + if (!portal) + { + portal = new HWHorizonPortal(pstate, horizon, di->Viewpoint, di); + di->Portals.Push(portal); + } + portal->AddLine(this); + break; + + case PORTALTYPE_SKYBOX: + portal = di->FindPortal(secportal); + if (!portal) + { + // either a regular skybox or an Eternity-style horizon + if (secportal->mType != PORTS_SKYVIEWPOINT) portal = new HWEEHorizonPortal(pstate, secportal, di); + else + { + portal = new HWScenePortal(pstate, new HWSkyboxPortal(secportal)); + di->Portals.Push(portal); + } + } + portal->AddLine(this); + break; + + case PORTALTYPE_SECTORSTACK: + portal = di->FindPortal(this->portal); + if (!portal) + { + portal = new HWScenePortal(pstate, new HWSectorStackPortal(this->portal)); + di->Portals.Push(portal); + } + portal->AddLine(this); + break; + + case PORTALTYPE_PLANEMIRROR: + if (pstate->PlaneMirrorMode * planemirror->fC() <= 0) + { + //@sync-portal + planemirror = pstate->UniquePlaneMirrors.Get(planemirror); + portal = di->FindPortal(planemirror); + if (!portal) + { + portal = new HWScenePortal(pstate, new HWPlaneMirrorPortal(planemirror)); + di->Portals.Push(portal); + } + portal->AddLine(this); + } + break; + + case PORTALTYPE_MIRROR: + portal = di->FindPortal(seg->linedef); + if (!portal) + { + portal = new HWScenePortal(pstate, new HWMirrorPortal(seg->linedef)); + di->Portals.Push(portal); + } + portal->AddLine(this); + if (gl_mirror_envmap) + { + // draw a reflective layer over the mirror + di->AddMirrorSurface(this); + } + break; + + case PORTALTYPE_LINETOLINE: + if (!lineportal) + return; + portal = di->FindPortal(lineportal); + if (!portal) + { + line_t *otherside = lineportal->lines[0]->mDestination; + if (otherside != nullptr && otherside->portalindex < level.linePortals.Size()) + { + di->ProcessActorsInPortal(otherside->getPortal()->mGroup, di->in_area); + } + portal = new HWScenePortal(pstate, new HWLineToLinePortal(lineportal)); + di->Portals.Push(portal); + } + portal->AddLine(this); + break; + + case PORTALTYPE_SKY: + sky = pstate->UniqueSkies.Get(sky); + portal = di->FindPortal(sky); + if (!portal) + { + portal = new HWSkyPortal(screen->mSkyData, pstate, sky); + di->Portals.Push(portal); + } + portal->AddLine(this); + break; + } + vertcount = 0; } //========================================================================== @@ -794,6 +1124,7 @@ void GLWall::DoMidTexture(HWDrawInfo *di, seg_t * seg, bool drawfogboundary, float texturetop, texturebottom; bool wrap = (seg->linedef->flags&ML_WRAP_MIDTEX) || (seg->sidedef->Flags&WALLF_WRAP_MIDTEX); bool mirrory = false; + float rowoffset = 0; // // @@ -812,7 +1143,7 @@ void GLWall::DoMidTexture(HWDrawInfo *di, seg_t * seg, bool drawfogboundary, tci.mRenderHeight = -tci.mRenderHeight; tci.mScale.Y = -tci.mScale.Y; } - float rowoffset = tci.RowOffset(seg->sidedef->GetTextureYOffset(side_t::mid)); + rowoffset = tci.RowOffset(seg->sidedef->GetTextureYOffset(side_t::mid)); if ((seg->linedef->flags & ML_DONTPEGBOTTOM) >0) { texturebottom = MAX(realfront->GetPlaneTexZ(sector_t::floor), realback->GetPlaneTexZ(sector_t::floor)) + rowoffset; @@ -843,10 +1174,10 @@ void GLWall::DoMidTexture(HWDrawInfo *di, seg_t * seg, bool drawfogboundary, if (!tex || tex->UseType==ETextureType::Null) { if (front->GetTexture(sector_t::ceiling) == skyflatnum && - back->GetTexture(sector_t::ceiling) == skyflatnum) + back->GetTexture(sector_t::ceiling) == skyflatnum && !wrap) { - // intra-sky lines do not clip the texture at all if there's no upper texture - topleft = topright = wrap ? 1e16f : texturetop; + // intra-sky lines do not clip the texture at all if there's no upper texture. + topleft = topright = texturetop; } else { @@ -1459,7 +1790,7 @@ void GLWall::Process(HWDrawInfo *di, seg_t *seg, sector_t * frontsector, sector_ sector_t * segback; #ifdef _DEBUG - if (seg->linedef->Index() == 3407) + if (seg->linedef->Index() == 14454) { int a = 0; } diff --git a/src/hwrenderer/scene/hw_walls_vertex.cpp b/src/hwrenderer/scene/hw_walls_vertex.cpp index b0042baff..70ba96369 100644 --- a/src/hwrenderer/scene/hw_walls_vertex.cpp +++ b/src/hwrenderer/scene/hw_walls_vertex.cpp @@ -267,7 +267,7 @@ void GLWall::MakeVertices(HWDrawInfo *di, bool nosplit) if (vertcount == 0) { bool split = (gl_seamless && !nosplit && seg->sidedef != nullptr && !(seg->sidedef->Flags & WALLF_POLYOBJ) && !(flags & GLWF_NOSPLIT)); - auto ret = di->AllocVertices(split ? CountVertices() : 4); + auto ret = screen->mVertexData->AllocVertices(split ? CountVertices() : 4); vertindex = ret.second; vertcount = CreateVertices(ret.first, split); } diff --git a/src/hwrenderer/scene/hw_weapon.cpp b/src/hwrenderer/scene/hw_weapon.cpp index 8e3650310..bdcf1f08e 100644 --- a/src/hwrenderer/scene/hw_weapon.cpp +++ b/src/hwrenderer/scene/hw_weapon.cpp @@ -34,6 +34,8 @@ #include "r_data/models/models.h" #include "hw_weapon.h" #include "hw_fakeflat.h" + +#include "hwrenderer/models/hw_models.h" #include "hwrenderer/dynlights/hw_dynlightdata.h" #include "hwrenderer/textures/hw_material.h" #include "hwrenderer/utility/hw_lighting.h" @@ -41,6 +43,8 @@ #include "hwrenderer/scene/hw_drawinfo.h" #include "hwrenderer/scene/hw_drawstructs.h" #include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/dynlights/hw_lightbuffer.h" +#include "hw_renderstate.h" EXTERN_CVAR(Float, transsouls) EXTERN_CVAR(Int, gl_fuzztype) @@ -48,6 +52,71 @@ EXTERN_CVAR(Bool, r_drawplayersprites) EXTERN_CVAR(Bool, r_deathcamera) +//========================================================================== +// +// R_DrawPSprite +// +//========================================================================== + +void HWDrawInfo::DrawPSprite(HUDSprite *huds, FRenderState &state) +{ + if (huds->RenderStyle.BlendOp == STYLEOP_Shadow) + { + state.SetColor(0.2f, 0.2f, 0.2f, 0.33f, huds->cm.Desaturation); + } + else + { + state.SetColor(huds->lightlevel, 0, isFullbrightScene(), huds->cm, huds->alpha, true); + } + state.SetLightIndex(-1); + state.SetRenderStyle(huds->RenderStyle); + state.SetTextureMode(huds->RenderStyle); + state.SetObjectColor(huds->ObjectColor); + state.SetDynLight(huds->dynrgb[0], huds->dynrgb[1], huds->dynrgb[2]); + state.EnableBrightmap(!(huds->RenderStyle.Flags & STYLEF_ColorIsFixed)); + + if (huds->mframe) + { + state.AlphaFunc(Alpha_GEqual, 0); + + FGLModelRenderer renderer(this, state, huds->lightindex); + renderer.RenderHUDModel(huds->weapon, huds->mx, huds->my); + state.SetVertexBuffer(screen->mVertexData); + } + else + { + float thresh = (huds->tex->tex->GetTranslucency() || huds->OverrideShader != -1) ? 0.f : gl_mask_sprite_threshold; + state.AlphaFunc(Alpha_GEqual, thresh); + state.SetMaterial(huds->tex, CLAMP_XY_NOMIP, 0, huds->OverrideShader); + state.Draw(DT_TriangleStrip, huds->mx, 4); + } + + state.SetTextureMode(TM_NORMAL); + state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); + state.SetObjectColor(0xffffffff); + state.SetDynLight(0, 0, 0); + state.EnableBrightmap(false); +} + +//========================================================================== +// +// R_DrawPlayerSprites +// +//========================================================================== + +void HWDrawInfo::DrawPlayerSprites(bool hudModelStep, FRenderState &state) +{ + int oldlightmode = level.lightmode; + if (!hudModelStep && level.lightmode == 8) level.lightmode = 2; // Software lighting cannot handle 2D content so revert to lightmode 2 for that. + for (auto &hudsprite : hudsprites) + { + if ((!!hudsprite.mframe) == hudModelStep) + DrawPSprite(&hudsprite, state); + } + level.lightmode = oldlightmode; +} + + //========================================================================== // // @@ -401,7 +470,7 @@ bool HUDSprite::GetWeaponRect(HWDrawInfo *di, DPSprite *psp, float sx, float sy, v2 = tex->GetSpriteVB(); } - auto verts = di->AllocVertices(4); + auto verts = screen->mVertexData->AllocVertices(4); mx = verts.second; verts.first[0].Set(x1, y1, 0, u1, v1); @@ -476,7 +545,7 @@ void HWDrawInfo::PreparePlayerSprites(sector_t * viewsector, area_t in_area) else { hw_GetDynModelLight(playermo, lightdata); - hudsprite.lightindex = UploadLights(lightdata); + hudsprite.lightindex = screen->mLights->UploadLights(lightdata); } } @@ -490,7 +559,7 @@ void HWDrawInfo::PreparePlayerSprites(sector_t * viewsector, area_t in_area) { if (!hudsprite.GetWeaponRect(this, psp, spos.X, spos.Y, player)) continue; } - AddHUDSprite(&hudsprite); + hudsprites.Push(hudsprite); } level.lightmode = oldlightmode; PrepareTargeterSprites(); @@ -537,8 +606,9 @@ void HWDrawInfo::PrepareTargeterSprites() hudsprite.weapon = psp; if (hudsprite.GetWeaponRect(this, psp, psp->x, psp->y, player)) { - AddHUDSprite(&hudsprite); + hudsprites.Push(hudsprite); } } } } + diff --git a/src/hwrenderer/textures/hw_ihwtexture.h b/src/hwrenderer/textures/hw_ihwtexture.h index b91291806..10447e5b1 100644 --- a/src/hwrenderer/textures/hw_ihwtexture.h +++ b/src/hwrenderer/textures/hw_ihwtexture.h @@ -27,5 +27,7 @@ public: virtual void Clean(bool all) = 0; virtual void CleanUnused(SpriteHits &usedtranslations) = 0; + + void Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data); }; diff --git a/src/hwrenderer/textures/hw_material.cpp b/src/hwrenderer/textures/hw_material.cpp index 905860204..48d313d95 100644 --- a/src/hwrenderer/textures/hw_material.cpp +++ b/src/hwrenderer/textures/hw_material.cpp @@ -31,7 +31,102 @@ EXTERN_CVAR(Bool, gl_texture_usehires) -extern TArray usershaders; +//=========================================================================== +// +// Quick'n dirty image rescaling. +// +// This will only be used when the source texture is larger than +// what the hardware can manage (extremely rare in Doom) +// +// Code taken from wxWidgets +// +//=========================================================================== + +struct BoxPrecalc +{ + int boxStart; + int boxEnd; +}; + +static void ResampleBoxPrecalc(TArray& boxes, int oldDim) +{ + int newDim = boxes.Size(); + const double scale_factor_1 = double(oldDim) / newDim; + const int scale_factor_2 = (int)(scale_factor_1 / 2); + + for (int dst = 0; dst < newDim; ++dst) + { + // Source pixel in the Y direction + const int src_p = int(dst * scale_factor_1); + + BoxPrecalc& precalc = boxes[dst]; + precalc.boxStart = clamp(int(src_p - scale_factor_1 / 2.0 + 1), 0, oldDim - 1); + precalc.boxEnd = clamp(MAX(precalc.boxStart + 1, int(src_p + scale_factor_2)), 0, oldDim - 1); + } +} + +void IHardwareTexture::Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data) +{ + + // This function implements a simple pre-blur/box averaging method for + // downsampling that gives reasonably smooth results To scale the image + // down we will need to gather a grid of pixels of the size of the scale + // factor in each direction and then do an averaging of the pixels. + + TArray vPrecalcs(height, true); + TArray hPrecalcs(width, true); + + ResampleBoxPrecalc(vPrecalcs, sheight); + ResampleBoxPrecalc(hPrecalcs, swidth); + + int averaged_pixels, averaged_alpha, src_pixel_index; + double sum_r, sum_g, sum_b, sum_a; + + for (int y = 0; y < height; y++) // Destination image - Y direction + { + // Source pixel in the Y direction + const BoxPrecalc& vPrecalc = vPrecalcs[y]; + + for (int x = 0; x < width; x++) // Destination image - X direction + { + // Source pixel in the X direction + const BoxPrecalc& hPrecalc = hPrecalcs[x]; + + // Box of pixels to average + averaged_pixels = 0; + averaged_alpha = 0; + sum_r = sum_g = sum_b = sum_a = 0.0; + + for (int j = vPrecalc.boxStart; j <= vPrecalc.boxEnd; ++j) + { + for (int i = hPrecalc.boxStart; i <= hPrecalc.boxEnd; ++i) + { + // Calculate the actual index in our source pixels + src_pixel_index = j * swidth + i; + + int a = src_data[src_pixel_index * 4 + 3]; + if (a > 0) // do not use color from fully transparent pixels + { + sum_r += src_data[src_pixel_index * 4 + 0]; + sum_g += src_data[src_pixel_index * 4 + 1]; + sum_b += src_data[src_pixel_index * 4 + 2]; + sum_a += a; + averaged_pixels++; + } + averaged_alpha++; + + } + } + + // Calculate the average from the sum and number of averaged pixels + dst_data[0] = (unsigned char)xs_CRoundToInt(sum_r / averaged_pixels); + dst_data[1] = (unsigned char)xs_CRoundToInt(sum_g / averaged_pixels); + dst_data[2] = (unsigned char)xs_CRoundToInt(sum_b / averaged_pixels); + dst_data[3] = (unsigned char)xs_CRoundToInt(sum_a / averaged_alpha); + dst_data += 4; + } + } +} //=========================================================================== // diff --git a/src/hwrenderer/textures/hw_material.h b/src/hwrenderer/textures/hw_material.h index d334ceaa1..427442629 100644 --- a/src/hwrenderer/textures/hw_material.h +++ b/src/hwrenderer/textures/hw_material.h @@ -50,8 +50,6 @@ struct FTexCoordInfo class FMaterial { - friend class FRenderState; - // This array is needed because not all textures are managed by the texture manager // but some code needs to discard all hardware dependent data attached to any created texture. // Font characters are not, for example. @@ -86,6 +84,7 @@ public: void SetSpriteRect(); void Precache(); void PrecacheList(SpriteHits &translations); + int GetShaderIndex() const { return mShaderIndex; } IHardwareTexture * ValidateSysTexture(FTexture * tex, bool expand); void AddTextureLayer(FTexture *tex) { diff --git a/src/hwrenderer/utility/hw_clock.cpp b/src/hwrenderer/utility/hw_clock.cpp index a34b1fdb1..179776a65 100644 --- a/src/hwrenderer/utility/hw_clock.cpp +++ b/src/hwrenderer/utility/hw_clock.cpp @@ -51,6 +51,7 @@ glcycle_t RenderAll; glcycle_t Dirty; glcycle_t drawcalls; glcycle_t twoD, Flush3D; +glcycle_t MTWait, WTTotal; int vertexcount, flatvertices, flatprimitives; int rendered_lines,rendered_flats,rendered_sprites,render_vertexsplit,render_texsplit,rendered_decals, rendered_portals; @@ -73,6 +74,8 @@ void ResetProfilingData() RenderSprite.Reset(); SetupSprite.Reset(); drawcalls.Reset(); + MTWait.Reset(); + WTTotal.Reset(); flatvertices=flatprimitives=vertexcount=0; render_texsplit=render_vertexsplit=rendered_lines=rendered_flats=rendered_sprites=rendered_decals=rendered_portals = 0; @@ -87,20 +90,22 @@ void ResetProfilingData() static void AppendRenderTimes(FString &str) { double setupwall = SetupWall.TimeMS(); - double clipwall = ClipWall.TimeMS() - SetupWall.TimeMS(); - double bsp = Bsp.TimeMS() - ClipWall.TimeMS() - SetupFlat.TimeMS() - SetupSprite.TimeMS(); + double clipwall = ClipWall.TimeMS(); + double bsp = Bsp.TimeMS() - ClipWall.TimeMS(); str.AppendFormat("BSP = %2.3f, Clip=%2.3f\n" "W: Render=%2.3f, Setup=%2.3f\n" "F: Render=%2.3f, Setup=%2.3f\n" "S: Render=%2.3f, Setup=%2.3f\n" "2D: %2.3f Finish3D: %2.3f\n" + "Main thread total=%2.3f, Main thread waiting=%2.3f Worker thread total=%2.3f, Worker thread waiting=%2.3f\n" "All=%2.3f, Render=%2.3f, Setup=%2.3f, Portal=%2.3f, Drawcalls=%2.3f, Postprocess=%2.3f, Finish=%2.3f\n", bsp, clipwall, RenderWall.TimeMS(), setupwall, RenderFlat.TimeMS(), SetupFlat.TimeMS(), RenderSprite.TimeMS(), SetupSprite.TimeMS(), twoD.TimeMS(), Flush3D.TimeMS() - twoD.TimeMS(), + MTWait.TimeMS() + Bsp.TimeMS(), MTWait.TimeMS(), WTTotal.TimeMS(), WTTotal.TimeMS() - setupwall - SetupFlat.TimeMS() - SetupSprite.TimeMS(), All.TimeMS() + Finish.TimeMS(), RenderAll.TimeMS(), ProcessAll.TimeMS(), PortalAll.TimeMS(), drawcalls.TimeMS(), PostProcess.TimeMS(), Finish.TimeMS()); } diff --git a/src/hwrenderer/utility/hw_clock.h b/src/hwrenderer/utility/hw_clock.h index 243ec4924..8dec4a6de 100644 --- a/src/hwrenderer/utility/hw_clock.h +++ b/src/hwrenderer/utility/hw_clock.h @@ -13,6 +13,7 @@ extern glcycle_t ProcessAll, PostProcess; extern glcycle_t RenderAll; extern glcycle_t Dirty; extern glcycle_t drawcalls, twoD, Flush3D; +extern glcycle_t MTWait, WTTotal; extern int iter_dlightf, iter_dlight, draw_dlight, draw_dlightf; extern int rendered_lines,rendered_flats,rendered_sprites,rendered_decals,render_vertexsplit,render_texsplit; diff --git a/src/hwrenderer/utility/hw_draw2d.cpp b/src/hwrenderer/utility/hw_draw2d.cpp new file mode 100644 index 000000000..f15ddc2c4 --- /dev/null +++ b/src/hwrenderer/utility/hw_draw2d.cpp @@ -0,0 +1,220 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** 2d drawer +** Renderer interface +** +*/ + +#include "doomstat.h" +#include "v_video.h" +#include "cmdlib.h" +#include "r_defs.h" +#include "hwrenderer/data/buffers.h" +#include "hwrenderer/data/flatvertices.h" +#include "hwrenderer/data/hw_viewpointbuffer.h" +#include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/utility/hw_cvars.h" +#include "hwrenderer/scene/hw_renderstate.h" +#include "r_videoscale.h" + + +//=========================================================================== +// +// Vertex buffer for 2D drawer +// +//=========================================================================== + +class F2DVertexBuffer +{ + IVertexBuffer *mVertexBuffer; + IIndexBuffer *mIndexBuffer; + + +public: + + F2DVertexBuffer() + { + mVertexBuffer = screen->CreateVertexBuffer(); + mIndexBuffer = screen->CreateIndexBuffer(); + + static const FVertexBufferAttribute format[] = { + { 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(F2DDrawer::TwoDVertex, x) }, + { 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(F2DDrawer::TwoDVertex, u) }, + { 0, VATTR_COLOR, VFmt_Byte4, (int)myoffsetof(F2DDrawer::TwoDVertex, color0) } + }; + mVertexBuffer->SetFormat(1, 3, sizeof(F2DDrawer::TwoDVertex), format); + } + ~F2DVertexBuffer() + { + delete mIndexBuffer; + delete mVertexBuffer; + } + + void UploadData(F2DDrawer::TwoDVertex *vertices, int vertcount, int *indices, int indexcount) + { + mVertexBuffer->SetData(vertcount * sizeof(*vertices), vertices, false); + mIndexBuffer->SetData(indexcount * sizeof(unsigned int), indices, false); + } + + std::pair GetBufferObjects() const + { + return std::make_pair(mVertexBuffer, mIndexBuffer); + } +}; + +//=========================================================================== +// +// Draws the 2D stuff. This is the version for OpenGL 3 and later. +// +//=========================================================================== + +CVAR(Bool, gl_aalines, false, CVAR_ARCHIVE) + +void Draw2D(F2DDrawer *drawer, FRenderState &state) +{ + twoD.Clock(); + + const auto &mScreenViewport = screen->mScreenViewport; + state.SetViewport(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height); + screen->mViewpoints->Set2D(state, screen->GetWidth(), screen->GetHeight()); + + state.EnableDepthTest(false); + state.EnableMultisampling(false); + state.EnableLineSmooth(gl_aalines); + + auto &vertices = drawer->mVertices; + auto &indices = drawer->mIndices; + auto &commands = drawer->mData; + + if (commands.Size() == 0) + { + twoD.Unclock(); + return; + } + + for (auto &v : vertices) + { + // Change from BGRA to RGBA + std::swap(v.color0.r, v.color0.b); + } + F2DVertexBuffer vb; + vb.UploadData(&vertices[0], vertices.Size(), &indices[0], indices.Size()); + state.SetVertexBuffer(&vb); + state.EnableFog(false); + + for(auto &cmd : commands) + { + + int gltrans = -1; + state.SetRenderStyle(cmd.mRenderStyle); + state.EnableBrightmap(!(cmd.mRenderStyle.Flags & STYLEF_ColorIsFixed)); + state.EnableFog(2); // Special 2D mode 'fog'. + + state.SetTextureMode(cmd.mDrawMode); + + int sciX, sciY, sciW, sciH; + if (cmd.mFlags & F2DDrawer::DTF_Scissor) + { + // scissor test doesn't use the current viewport for the coordinates, so use real screen coordinates + // Note that the origin here is the lower left corner! + sciX = screen->ScreenToWindowX(cmd.mScissor[0]); + sciY = screen->ScreenToWindowY(cmd.mScissor[3]); + sciW = screen->ScreenToWindowX(cmd.mScissor[2]) - sciX; + sciH = screen->ScreenToWindowY(cmd.mScissor[1]) - sciY; + } + else + { + sciX = sciY = sciW = sciH = -1; + } + state.SetScissor(sciX, sciY, sciW, sciH); + + if (cmd.mSpecialColormap[0].a != 0) + { + state.SetTextureMode(TM_FIXEDCOLORMAP); + state.SetObjectColor(cmd.mSpecialColormap[0]); + state.SetObjectColor2(cmd.mSpecialColormap[1]); + } + state.SetFog(cmd.mColor1, 0); + state.SetColor(1, 1, 1, 1, cmd.mDesaturate); + + state.AlphaFunc(Alpha_GEqual, 0.f); + + if (cmd.mTexture != nullptr) + { + auto mat = FMaterial::ValidateTexture(cmd.mTexture, false); + if (mat == nullptr) continue; + + if (gltrans == -1 && cmd.mTranslation != nullptr) gltrans = cmd.mTranslation->GetUniqueIndex(); + state.SetMaterial(mat, cmd.mFlags & F2DDrawer::DTF_Wrap ? CLAMP_NONE : CLAMP_XY_NOMIP, -gltrans, -1); + state.EnableTexture(true); + + // Canvas textures are stored upside down + if (cmd.mTexture->bHasCanvas) + { + state.mTextureMatrix.loadIdentity(); + state.mTextureMatrix.scale(1.f, -1.f, 1.f); + state.mTextureMatrix.translate(0.f, 1.f, 0.0f); + state.EnableTextureMatrix(true); + } + if (cmd.mFlags & F2DDrawer::DTF_Burn) + { + state.SetEffect(EFF_BURN); + } + } + else + { + state.EnableTexture(false); + } + + switch (cmd.mType) + { + case F2DDrawer::DrawTypeTriangles: + state.DrawIndexed(DT_Triangles, cmd.mIndexIndex, cmd.mIndexCount); + break; + + case F2DDrawer::DrawTypeLines: + state.Draw(DT_Lines, cmd.mVertIndex, cmd.mVertCount); + break; + + case F2DDrawer::DrawTypePoints: + state.Draw(DT_Points, cmd.mVertIndex, cmd.mVertCount); + break; + + } + state.SetObjectColor(0xffffffff); + state.SetObjectColor2(0); + state.EnableTextureMatrix(false); + state.SetEffect(EFF_NONE); + + } + state.SetScissor(-1, -1, -1, -1); + + state.SetRenderStyle(STYLE_Translucent); + state.SetVertexBuffer(screen->mVertexData); + state.EnableTexture(true); + state.EnableBrightmap(true); + state.SetTextureMode(TM_NORMAL); + state.EnableFog(false); + state.ResetColor(); + twoD.Unclock(); +} diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp index 8d49ddc2d..8211cc57f 100644 --- a/src/menu/loadsavemenu.cpp +++ b/src/menu/loadsavemenu.cpp @@ -535,13 +535,10 @@ void FSavegameManager::UnloadSaveData() { delete SavePic; } - if (SaveComment != nullptr) - { - V_FreeBrokenLines(SaveComment); - } + SaveComment.Clear(); + SaveComment.ShrinkToFit(); SavePic = nullptr; - SaveComment = nullptr; SavePicData.Clear(); } @@ -615,7 +612,7 @@ void FSavegameManager::DrawSaveComment(FFont *font, int cr, int x, int y, int sc // I'm not sure why SaveComment would go nullptr in this loop, but I got // a crash report where it was nullptr when i reached 1, so now I check // for that. - for (int i = 0; SaveComment != nullptr && SaveComment[i].Width >= 0 && i < maxlines; ++i) + for (int i = 0; i < maxlines && (unsigned)i < SaveComment.Size(); ++i) { screen->DrawText(font, cr, x, y + font->GetHeight() * i * scalefactor, SaveComment[i].Text, DTA_CleanNoMove, true, TAG_DONE); } @@ -647,14 +644,9 @@ void FSavegameManager::SetFileInfo(int Selected) { if (!SaveGames[Selected]->Filename.IsEmpty()) { - char workbuf[512]; - - mysnprintf(workbuf, countof(workbuf), "File on disk:\n%s", SaveGames[Selected]->Filename.GetChars()); - if (SaveComment != nullptr) - { - V_FreeBrokenLines(SaveComment); - } - SaveComment = V_BreakLines(SmallFont, WindowSize, workbuf); + FString work; + work.Format("File on disk:\n%s", SaveGames[Selected]->Filename.GetChars()); + SaveComment = V_BreakLines(SmallFont, WindowSize, work); } } diff --git a/src/menu/menu.h b/src/menu/menu.h index b374d29e1..83f4f66da 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -73,7 +73,7 @@ private: int LastAccessed = -1; TArray SavePicData; FTexture *SavePic = nullptr; - FBrokenLines *SaveComment = nullptr; + TArray SaveComment; public: int WindowSize = 0; diff --git a/src/menu/playermenu.cpp b/src/menu/playermenu.cpp index 507470e57..973a49fdf 100644 --- a/src/menu/playermenu.cpp +++ b/src/menu/playermenu.cpp @@ -207,7 +207,13 @@ DEFINE_ACTION_FUNCTION(DPlayerMenu, GenderChanged) // only allow if the menu is active to prevent abuse. if (self == CurrentMenu) { - cvar_set("gender", v == 0 ? "male" : v == 1 ? "female" : "other"); + switch(v) + { + case 0: cvar_set("gender", "male"); break; + case 1: cvar_set("gender", "female"); break; + case 2: cvar_set("gender", "neutral"); break; + case 3: cvar_set("gender", "other"); break; + } } return 0; } diff --git a/src/namedef.h b/src/namedef.h index b098a06c0..c66890551 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -668,6 +668,14 @@ xx(Prev) xx(Children) xx(Owner) +xx(HealthFloor) +xx(HealthCeiling) +xx(DamageSpecial) +xx(DeathSpecial) +xx(HealthFloorGroup) +xx(HealthCeilingGroup) +xx(HealthGroup) + // USDF keywords xx(Amount) xx(Text) diff --git a/src/p_3dfloors.cpp b/src/p_3dfloors.cpp index 5c6096f84..47506bfc0 100644 --- a/src/p_3dfloors.cpp +++ b/src/p_3dfloors.cpp @@ -345,7 +345,7 @@ void P_PlayerOnSpecial3DFloor(player_t* player) // Checks whether the player's feet touch a solid 3D floor in the sector // //========================================================================== -bool P_CheckFor3DFloorHit(AActor * mo, double z) +bool P_CheckFor3DFloorHit(AActor * mo, double z, bool trigger) { if ((mo->player && (mo->player->cheats & CF_PREDICTING))) return false; @@ -357,7 +357,8 @@ bool P_CheckFor3DFloorHit(AActor * mo, double z) { if (fabs(z - rover->top.plane->ZatPoint(mo)) < EQUAL_EPSILON) { - rover->model->TriggerSectorActions (mo, SECSPAC_HitFloor); + mo->BlockingFloor = rover->model; + if (trigger) rover->model->TriggerSectorActions (mo, SECSPAC_HitFloor); return true; } } @@ -371,7 +372,7 @@ bool P_CheckFor3DFloorHit(AActor * mo, double z) // Checks whether the player's head touches a solid 3D floor in the sector // //========================================================================== -bool P_CheckFor3DCeilingHit(AActor * mo, double z) +bool P_CheckFor3DCeilingHit(AActor * mo, double z, bool trigger) { if ((mo->player && (mo->player->cheats & CF_PREDICTING))) return false; @@ -383,7 +384,8 @@ bool P_CheckFor3DCeilingHit(AActor * mo, double z) { if(fabs(z - rover->bottom.plane->ZatPoint(mo)) < EQUAL_EPSILON) { - rover->model->TriggerSectorActions (mo, SECSPAC_HitCeiling); + mo->BlockingCeiling = rover->model; + if (trigger) rover->model->TriggerSectorActions (mo, SECSPAC_HitCeiling); return true; } } diff --git a/src/p_3dfloors.h b/src/p_3dfloors.h index f46aa913e..e260f9f5f 100644 --- a/src/p_3dfloors.h +++ b/src/p_3dfloors.h @@ -117,8 +117,8 @@ struct lightlist_t class player_t; void P_PlayerOnSpecial3DFloor(player_t* player); -bool P_CheckFor3DFloorHit(AActor * mo, double z); -bool P_CheckFor3DCeilingHit(AActor * mo, double z); +bool P_CheckFor3DFloorHit(AActor * mo, double z, bool trigger); +bool P_CheckFor3DCeilingHit(AActor * mo, double z, bool trigger); void P_Recalculate3DFloors(sector_t *); void P_RecalculateAttached3DFloors(sector_t * sec); void P_RecalculateLights(sector_t *sector); diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 445c8125f..712cddf1b 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -4975,6 +4975,8 @@ enum EACSFunctions ACSF_Ceil, ACSF_ScriptCall, ACSF_StartSlideshow, + ACSF_GetSectorHealth, + ACSF_GetLineHealth, // Eternity's ACSF_GetLineX = 300, @@ -6841,6 +6843,44 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) G_StartSlideshow(FName(FBehavior::StaticLookupString(args[0]))); break; + case ACSF_GetSectorHealth: + { + int part = args[1]; + FSectorTagIterator it(args[0]); + int s = it.Next(); + if (s < 0) + return 0; + sector_t* ss = &level.sectors[s]; + FHealthGroup* grp; + if (part == SECPART_Ceiling) + { + return (ss->healthceilinggroup && (grp = P_GetHealthGroup(ss->healthceilinggroup))) + ? grp->health : ss->healthceiling; + } + else if (part == SECPART_Floor) + { + return (ss->healthfloorgroup && (grp = P_GetHealthGroup(ss->healthfloorgroup))) + ? grp->health : ss->healthfloor; + } + return 0; + } + + case ACSF_GetLineHealth: + { + FLineIdIterator it(args[0]); + int l = it.Next(); + if (l < 0) + return 0; + line_t* ll = &level.lines[l]; + if (ll->healthgroup > 0) + { + FHealthGroup* grp = P_GetHealthGroup(ll->healthgroup); + if (grp) return grp->health; + } + + return ll->health; + } + case ACSF_GetLineX: case ACSF_GetLineY: { diff --git a/src/p_ceiling.cpp b/src/p_ceiling.cpp index 5eb8157f5..0f37e3379 100644 --- a/src/p_ceiling.cpp +++ b/src/p_ceiling.cpp @@ -428,7 +428,7 @@ bool P_CreateCeiling(sector_t *sec, DCeiling::ECeiling type, line_t *line, int t switch (change & 3) { case 1: // type is zeroed - ceiling->m_NewSpecial.Clear(); + ceiling->m_NewSpecial = {}; ceiling->m_Type = DCeiling::genCeilingChg0; break; case 2: // type is copied @@ -447,7 +447,7 @@ bool P_CreateCeiling(sector_t *sec, DCeiling::ECeiling type, line_t *line, int t switch (change & 3) { case 1: // type is zeroed - ceiling->m_NewSpecial.Clear(); + ceiling->m_NewSpecial = {}; ceiling->m_Type = DCeiling::genCeilingChg0; break; case 2: // type is copied diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index cabb85edf..c577a4c2c 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -715,27 +715,6 @@ DEFINE_ACTION_FUNCTION(DConversationMenu, SendConversationReply) } -// Needed for the conversion process. -class DBrokenLines : public DObject -{ - DECLARE_ABSTRACT_CLASS(DBrokenLines, DObject) - -public: - FBrokenLines *mBroken; - unsigned int mCount; - - DBrokenLines(FBrokenLines *broken, unsigned int count) - { - mBroken = broken; - mCount = count; - } - - void OnDestroy() override - { - V_FreeBrokenLines(mBroken); - } -}; - //============================================================================ // // P_FreeStrifeConversations diff --git a/src/p_destructible.cpp b/src/p_destructible.cpp new file mode 100755 index 000000000..039ee302c --- /dev/null +++ b/src/p_destructible.cpp @@ -0,0 +1,580 @@ +#include "p_spec.h" +#include "g_levellocals.h" +#include "p_destructible.h" +#include "v_text.h" +#include "actor.h" +#include "p_trace.h" +#include "p_lnspec.h" +#include "r_sky.h" +#include "p_local.h" +#include "p_maputl.h" +#include "c_cvars.h" +#include "serializer.h" + +//========================================================================== +// +// [ZZ] Geometry damage logic callbacks +// +//========================================================================== +void P_SetHealthGroupHealth(int group, int health) +{ + FHealthGroup* grp = P_GetHealthGroup(group); + if (!grp) return; + + grp->health = health; + + // now set health of all linked objects to the same as this one + for (unsigned i = 0; i < grp->lines.Size(); i++) + { + line_t* lline = grp->lines[i]; + lline->health = health; + } + // + for (unsigned i = 0; i < grp->sectors.Size(); i++) + { + sector_t* lsector = grp->sectors[i]; + if (lsector->healthceilinggroup == group) + lsector->healthceiling = health; + if (lsector->healthfloorgroup == group) + lsector->healthfloor = health; + } +} + +void P_DamageHealthGroup(FHealthGroup* grp, void* object, AActor* source, int damage, FName damagetype, int side, int part, DVector3 position) +{ + if (!grp) return; + int group = grp->id; + + // now set health of all linked objects to the same as this one + for (unsigned i = 0; i < grp->lines.Size(); i++) + { + line_t* lline = grp->lines[i]; + if (lline == object) + continue; + lline->health = grp->health + damage; + P_DamageLinedef(lline, source, damage, damagetype, side, position, false); + } + // + for (unsigned i = 0; i < grp->sectors.Size(); i++) + { + sector_t* lsector = grp->sectors[i]; + + if (lsector->healthceilinggroup == group && (lsector != object || part != SECPART_Ceiling)) + { + lsector->healthceiling = grp->health + damage; + P_DamageSector(lsector, source, damage, damagetype, SECPART_Ceiling, position, false); + } + + if (lsector->healthfloorgroup == group && (lsector != object || part != SECPART_Floor)) + { + lsector->healthfloor = grp->health + damage; + P_DamageSector(lsector, source, damage, damagetype, SECPART_Floor, position, false); + } + } +} + +void P_DamageLinedef(line_t* line, AActor* source, int damage, FName damagetype, int side, DVector3 position, bool dogroups) +{ + line->health -= damage; + if (line->health < 0) line->health = 0; + + // callbacks here + // first off, call special if needed + if ((line->activation & SPAC_Damage) || ((line->activation & SPAC_Death) && !line->health)) + { + int activateon = SPAC_Damage; + if (!line->health) activateon |= SPAC_Death; + P_ActivateLine(line, source, side, activateon&line->activation, &position); + } + + if (dogroups && line->healthgroup) + { + FHealthGroup* grp = P_GetHealthGroup(line->healthgroup); + if (grp) + grp->health = line->health; + P_DamageHealthGroup(grp, line, source, damage, damagetype, side, -1, position); + } + + //Printf("P_DamageLinedef: %d damage (type=%s, source=%p), new health = %d\n", damage, damagetype.GetChars(), source, line->health); +} + +void P_DamageSector(sector_t* sector, AActor* source, int damage, FName damagetype, int part, DVector3 position, bool dogroups) +{ + int sectorhealth = (part == SECPART_Ceiling) ? sector->healthceiling : sector->healthfloor; + int newhealth = sectorhealth - damage; + if (newhealth < 0) newhealth = 0; + if (part == SECPART_Ceiling) + sector->healthceiling = newhealth; + else sector->healthfloor = newhealth; + + // callbacks here + int dmg = (part == SECPART_Ceiling) ? SECSPAC_DamageCeiling : SECSPAC_DamageFloor; + int dth = (part == SECPART_Ceiling) ? SECSPAC_DeathCeiling : SECSPAC_DeathFloor; + if (sector->SecActTarget) + { + sector->TriggerSectorActions(source, dmg); + if (newhealth == 0) + sector->TriggerSectorActions(source, dth); + } + + int group = (part == SECPART_Ceiling) ? sector->healthceilinggroup : sector->healthfloorgroup; + if (dogroups && group) + { + FHealthGroup* grp = P_GetHealthGroup(group); + if (grp) + grp->health = newhealth; + P_DamageHealthGroup(grp, sector, source, damage, damagetype, 0, part, position); + } + + //Printf("P_DamageSector: %d damage (type=%s, position=%s, source=%p), new health = %d\n", damage, damagetype.GetChars(), (part == SECPART_Ceiling) ? "ceiling" : "floor", source, newhealth); +} + + +//=========================================================================== +// +// +// +//=========================================================================== + +FHealthGroup* P_GetHealthGroup(int id) +{ + FHealthGroup* grp = level.healthGroups.CheckKey(id); + return grp; +} + +FHealthGroup* P_GetHealthGroupOrNew(int id, int health) +{ + FHealthGroup* grp = level.healthGroups.CheckKey(id); + if (!grp) + { + FHealthGroup newgrp; + newgrp.id = id; + newgrp.health = health; + grp = &(level.healthGroups.Insert(id, newgrp)); + } + return grp; +} + +void P_InitHealthGroups() +{ + level.healthGroups.Clear(); + TArray groupsInError; + for (unsigned i = 0; i < level.lines.Size(); i++) + { + line_t* lline = &level.lines[i]; + if (lline->healthgroup > 0) + { + FHealthGroup* grp = P_GetHealthGroupOrNew(lline->healthgroup, lline->health); + if (grp->health != lline->health) + { + Printf(TEXTCOLOR_RED "Warning: line %d in health group %d has different starting health (line health = %d, group health = %d)\n", i, lline->healthgroup, lline->health, grp->health); + groupsInError.Push(lline->healthgroup); + if (lline->health > grp->health) + grp->health = lline->health; + } + grp->lines.Push(lline); + } + } + for (unsigned i = 0; i < level.sectors.Size(); i++) + { + sector_t* lsector = &level.sectors[i]; + if (lsector->healthceilinggroup > 0) + { + FHealthGroup* grp = P_GetHealthGroupOrNew(lsector->healthceilinggroup, lsector->healthceiling); + if (grp->health != lsector->healthceiling) + { + Printf(TEXTCOLOR_RED "Warning: sector ceiling %d in health group %d has different starting health (ceiling health = %d, group health = %d)\n", i, lsector->healthceilinggroup, lsector->healthceiling, grp->health); + groupsInError.Push(lsector->healthceilinggroup); + if (lsector->healthceiling > grp->health) + grp->health = lsector->healthceiling; + } + grp->sectors.Push(lsector); + } + if (lsector->healthfloorgroup > 0) + { + FHealthGroup* grp = P_GetHealthGroupOrNew(lsector->healthfloorgroup, lsector->healthfloor); + if (grp->health != lsector->healthfloor) + { + Printf(TEXTCOLOR_RED "Warning: sector floor %d in health group %d has different starting health (floor health = %d, group health = %d)\n", i, lsector->healthfloorgroup, lsector->healthfloor, grp->health); + groupsInError.Push(lsector->healthfloorgroup); + if (lsector->healthfloor > grp->health) + grp->health = lsector->healthfloor; + } + if (lsector->healthceilinggroup != lsector->healthfloorgroup) + grp->sectors.Push(lsector); + } + } + for (unsigned i = 0; i < groupsInError.Size(); i++) + { + FHealthGroup* grp = P_GetHealthGroup(groupsInError[i]); + Printf(TEXTCOLOR_GOLD "Health group %d is using the highest found health value of %d", groupsInError[i], grp->health); + } +} + +//========================================================================== +// +// P_GeometryLineAttack +// +// Applies hitscan damage to geometry (lines and sectors with health>0) +//========================================================================== + +void P_GeometryLineAttack(FTraceResults& trace, AActor* thing, int damage, FName damageType) +{ + // [ZZ] hitscan geometry damage logic + // + if (trace.HitType == TRACE_HitWall && P_CheckLinedefVulnerable(trace.Line, trace.Side)) + { + if (trace.Tier == TIER_Lower || trace.Tier == TIER_Upper) // process back sector health if any + { + sector_t* backsector = (trace.Line->sidedef[!trace.Side] ? trace.Line->sidedef[!trace.Side]->sector : nullptr); + int sectorhealth = 0; + if (backsector && trace.Tier == TIER_Lower && backsector->healthfloor > 0 && P_CheckLinedefVulnerable(trace.Line, trace.Side, SECPART_Floor)) + sectorhealth = backsector->healthfloor; + if (backsector && trace.Tier == TIER_Upper && backsector->healthceiling > 0 && P_CheckLinedefVulnerable(trace.Line, trace.Side, SECPART_Ceiling)) + sectorhealth = backsector->healthceiling; + if (sectorhealth > 0) + { + P_DamageSector(backsector, thing, damage, damageType, (trace.Tier == TIER_Upper) ? SECPART_Ceiling : SECPART_Floor, trace.HitPos); + } + } + // always process linedef health if any + if (trace.Line->health > 0) + { + P_DamageLinedef(trace.Line, thing, damage, damageType, trace.Side, trace.HitPos); + } + // fake floors are not handled + } + else if (trace.HitType == TRACE_HitFloor || trace.HitType == TRACE_HitCeiling) + { + int sectorhealth = 0; + if (trace.HitType == TRACE_HitFloor && trace.Sector->healthfloor > 0 && P_CheckSectorVulnerable(trace.Sector, SECPART_Floor)) + sectorhealth = trace.Sector->healthfloor; + if (trace.HitType == TRACE_HitCeiling && trace.Sector->healthceiling > 0 && P_CheckSectorVulnerable(trace.Sector, SECPART_Ceiling)) + sectorhealth = trace.Sector->healthceiling; + if (sectorhealth > 0) + { + P_DamageSector(trace.Sector, thing, damage, damageType, (trace.HitType == TRACE_HitCeiling) ? SECPART_Ceiling : SECPART_Floor, trace.HitPos); + } + } +} + +//========================================================================== +// +// P_GeometryRadiusAttack +// +// Applies radius damage to destructible geometry (lines and sectors with health>0) +//========================================================================== + +struct pgra_data_t +{ + DVector3 pos; + line_t* line; + sector_t* sector; + int secpart; +}; + +// we use this horizontally for 2D wall distance, and in a bit fancier way for 3D wall distance +static DVector2 PGRA_ClosestPointOnLine2D(DVector2 x, DVector2 p1, DVector2 p2) +{ + DVector2 p2p1 = (p2 - p1); + DVector2 xp1 = (x - p1); + double r = p2p1 | xp1; + double len = p2p1.Length(); + r /= len; + + if (r < 0) + return p1; + if (r > len) + return p2; + + return p1 + p2p1.Unit() * r; +} + +static void PGRA_InsertIfCloser(TMap& damageGroupPos, int group, DVector3 pt, DVector3 check, sector_t* checksector, sector_t* sector, line_t* line, int secpart) +{ + // simple solid geometry sight check between "check" and "pt" + // expected - Trace hits nothing + DVector3 ptVec = (pt - check); + double ptDst = ptVec.Length() - 0.5; + ptVec.MakeUnit(); + FTraceResults res; + bool isblocked = Trace(check, checksector, ptVec, ptDst, 0, 0xFFFFFFFF, nullptr, res); + if (isblocked) return; + + pgra_data_t* existing = damageGroupPos.CheckKey(group); + // not present or distance is closer + if (!existing || (((*existing).pos - check).Length() > (pt - check).Length())) + { + if (!existing) existing = &damageGroupPos.Insert(group, pgra_data_t()); + existing->pos = pt; + existing->line = line; + existing->sector = sector; + existing->secpart = secpart; + } +} + +EXTERN_CVAR(Float, splashfactor); + +void P_GeometryRadiusAttack(AActor* bombspot, AActor* bombsource, int bombdamage, int bombdistance, FName damagetype, int fulldamagedistance) +{ + TMap damageGroupPos; + + double bombdistancefloat = 1. / (double)(bombdistance - fulldamagedistance); + + // now, this is not entirely correct... but sector actions still _do_ require a valid source actor to trigger anything + if (!bombspot) + return; + if (!bombsource) + bombsource = bombspot; + + // check current sector floor and ceiling. this is the only *sector* checked, otherwise only use lines + // a bit imprecise, but should work + sector_t* srcsector = bombspot->Sector; + + if (srcsector->healthceiling > 0) + { + double dstceiling = srcsector->ceilingplane.Normal() | (bombspot->Pos() + srcsector->ceilingplane.Normal()*srcsector->ceilingplane.D); + DVector3 spotTo = bombspot->Pos() - srcsector->ceilingplane.Normal() * dstceiling; + int grp = srcsector->healthceilinggroup; + if (grp <= 0) + grp = 0x80000000 | (srcsector->sectornum & 0x7FFFFFFF); + PGRA_InsertIfCloser(damageGroupPos, grp, spotTo, bombspot->Pos(), srcsector, srcsector, nullptr, SECPART_Ceiling); + } + + if (srcsector->healthfloor > 0) + { + double dstfloor = srcsector->floorplane.Normal() | (bombspot->Pos() + srcsector->floorplane.Normal()*srcsector->floorplane.D); + DVector3 spotTo = bombspot->Pos() - srcsector->floorplane.Normal() * dstfloor; + int grp = srcsector->healthfloorgroup; + if (grp <= 0) + grp = 0x40000000 | (srcsector->sectornum & 0x7FFFFFFF); + PGRA_InsertIfCloser(damageGroupPos, grp, spotTo, bombspot->Pos(), srcsector, srcsector, nullptr, SECPART_Floor); + } + + // enumerate all lines around + FBoundingBox bombbox(bombspot->X(), bombspot->Y(), bombdistance * 16); + FBlockLinesIterator it(bombbox); + line_t* ln; + int vc = validcount; + TArray lines; + while ((ln = it.Next())) // iterator and Trace both use validcount and interfere with each other + lines.Push(ln); + for (unsigned i = 0; i < lines.Size(); i++) + { + ln = lines[i]; + DVector2 pos2d = bombspot->Pos().XY(); + int sd = P_PointOnLineSide(pos2d, ln); + + side_t* side = ln->sidedef[sd]; + if (!side) continue; + sector_t* sector = side->sector; + side_t* otherside = ln->sidedef[!sd]; + sector_t* othersector = otherside ? otherside->sector : nullptr; + if (!ln->health && (!othersector || (!othersector->healthfloor && !othersector->healthceiling))) + continue; // non-interactive geometry + + DVector2 to2d = PGRA_ClosestPointOnLine2D(bombspot->Pos().XY(), side->V1()->p, side->V2()->p); + // this gives us x/y + double distto2d = (to2d - pos2d).Length(); + double z_top1, z_top2, z_bottom1, z_bottom2; // here, z_top1 is closest to the ceiling, and z_bottom1 is closest to the floor. + z_top1 = sector->ceilingplane.ZatPoint(to2d); + z_bottom1 = sector->floorplane.ZatPoint(to2d); + DVector3 to3d_fullheight(to2d.X, to2d.Y, clamp(bombspot->Z(), z_bottom1, z_top1)); + + if (ln->health && P_CheckLinedefVulnerable(ln, sd)) + { + bool cantdamage = false; + bool linefullheight = othersector && !!(ln->flags & (ML_BLOCKEVERYTHING)); + // decide specific position to affect on a line. + if (!linefullheight) + { + z_top2 = othersector->ceilingplane.ZatPoint(to2d); + z_bottom2 = othersector->floorplane.ZatPoint(to2d); + double bombz = bombspot->Z(); + if (z_top2 > z_top1) + z_top2 = z_top1; + if (z_bottom2 < z_bottom1) + z_bottom2 = z_bottom1; + if (bombz <= z_top2 && bombz >= z_bottom2) // between top and bottom opening + to3d_fullheight.Z = (z_top2 - bombz < bombz - z_bottom2) ? z_top2 : z_bottom2; + else if (bombz < z_bottom2 && bombz >= z_bottom1) + to3d_fullheight.Z = clamp(bombz, z_bottom1, z_bottom2); + else if (bombz > z_top2 && bombz <= z_top1) + to3d_fullheight.Z = clamp(bombz, z_top2, z_top1); + else cantdamage = true; + } + + if (!cantdamage) + { + if (ln->healthgroup) + { + PGRA_InsertIfCloser(damageGroupPos, ln->healthgroup, to3d_fullheight, bombspot->Pos(), srcsector, nullptr, ln, -1); + } + else + { + // otherwise just damage line + double dst = (to3d_fullheight - bombspot->Pos()).Length(); + int damage = 0; + if (dst < bombdistance) + { + dst = clamp(dst - (double)fulldamagedistance, 0, dst); + damage = (int)((double)bombdamage * (1. - dst * bombdistancefloat)); + if (bombsource == bombspot) + damage = (int)(damage * splashfactor); + } + P_DamageLinedef(ln, bombsource, damage, damagetype, sd, to3d_fullheight); + } + } + } + + if (othersector && othersector->healthceiling && P_CheckLinedefVulnerable(ln, sd, SECPART_Ceiling)) + { + z_top2 = othersector->ceilingplane.ZatPoint(to2d); + if (z_top2 < z_top1) // we have front side to hit against + { + DVector3 to3d_upper(to2d.X, to2d.Y, clamp(bombspot->Z(), z_top2, z_top1)); + int grp = othersector->healthceilinggroup; + if (grp <= 0) + grp = 0x80000000 | (othersector->sectornum & 0x7FFFFFFF); + PGRA_InsertIfCloser(damageGroupPos, grp, to3d_upper, bombspot->Pos(), srcsector, othersector, nullptr, SECPART_Ceiling); + } + } + + if (othersector && othersector->healthfloor && P_CheckLinedefVulnerable(ln, sd, SECPART_Floor)) + { + z_bottom2 = othersector->floorplane.ZatPoint(to2d); + if (z_bottom2 > z_bottom1) // we have front side to hit against + { + DVector3 to3d_lower(to2d.X, to2d.Y, clamp(bombspot->Z(), z_bottom1, z_bottom2)); + int grp = othersector->healthfloorgroup; + if (grp <= 0) + grp = 0x40000000 | (othersector->sectornum & 0x7FFFFFFF); + PGRA_InsertIfCloser(damageGroupPos, grp, to3d_lower, bombspot->Pos(), srcsector, othersector, nullptr, SECPART_Floor); + } + } + } + + // damage health groups and sectors. + // if group & 0x80000000, this is sector ceiling without health group + // if grpup & 0x40000000, this is sector floor without health group + // otherwise this is some object in health group + TMap::ConstIterator damageGroupIt(damageGroupPos); + TMap::ConstPair* damageGroupPair; + while (damageGroupIt.NextPair(damageGroupPair)) + { + DVector3 pos = damageGroupPair->Value.pos; + double dst = (pos - bombspot->Pos()).Length(); + int damage = 0; + if (dst < bombdistance) + { + dst = clamp(dst - (double)fulldamagedistance, 0, dst); + damage = (int)((double)bombdamage * (1. - dst * bombdistancefloat)); + if (bombsource == bombspot) + damage = (int)(damage * splashfactor); + } + + // for debug + //P_SpawnParticle(damageGroupPair->Value.pos, DVector3(), DVector3(), PalEntry(0xFF, 0x00, 0x00), 1.0, 35, 5.0, 0.0, 0.0, 0); + + int grp = damageGroupPair->Key; + if (grp & 0x80000000) // sector ceiling + { + assert(damageGroupPair->Value.sector != nullptr); + P_DamageSector(damageGroupPair->Value.sector, bombsource, damage, damagetype, SECPART_Ceiling, pos); + } + else if (grp & 0x40000000) // sector floor + { + assert(damageGroupPair->Value.sector != nullptr); + P_DamageSector(damageGroupPair->Value.sector, bombsource, damage, damagetype, SECPART_Floor, pos); + } + else + { + assert((damageGroupPair->Value.sector != nullptr) != (damageGroupPair->Value.line != nullptr)); + if (damageGroupPair->Value.line != nullptr) + P_DamageLinedef(damageGroupPair->Value.line, bombsource, damage, damagetype, P_PointOnLineSide(pos.XY(), damageGroupPair->Value.line), pos); + else P_DamageSector(damageGroupPair->Value.sector, bombsource, damage, damagetype, damageGroupPair->Value.secpart, pos); + } + } +} + +//========================================================================== +// +// P_CheckLinedefVulnerable +// +// If sectorpart is <0: returns if linedef is damageable directly +// If sectorpart is valid (SECPART_ enum): returns if sector on sidedef is +// damageable through this line at specified sectorpart +//========================================================================== + +bool P_CheckLinedefVulnerable(line_t* line, int side, int sectorpart) +{ + if (line->special == Line_Horizon) + return false; + side_t* sidedef = line->sidedef[side]; + if (!sidedef) + return false; + return true; +} + +//========================================================================== +// +// P_CheckSectorVulnerable +// +// Returns true if sector's floor or ceiling is directly damageable +//========================================================================== + +bool P_CheckSectorVulnerable(sector_t* sector, int part) +{ + FTextureID texture = sector->GetTexture((part == SECPART_Ceiling) ? sector_t::ceiling : sector_t::floor); + secplane_t* plane = (part == SECPART_Ceiling) ? §or->ceilingplane : §or->floorplane; + if (texture == skyflatnum) + return false; + return true; +} + +/// + +FSerializer &Serialize(FSerializer &arc, const char *key, FHealthGroup& g, FHealthGroup *def) +{ + if (arc.BeginObject(key)) + { + arc("id", g.id) + ("health", g.health) + .EndObject(); + } + return arc; +} + +void P_SerializeHealthGroups(FSerializer& arc) +{ + // todo : stuff + if (arc.BeginArray("healthgroups")) + { + if (arc.isReading()) + { + TArray readGroups; + int sz = arc.ArraySize(); + for (int i = 0; i < sz; i++) + { + FHealthGroup grp; + arc(nullptr, grp); + FHealthGroup* existinggrp = P_GetHealthGroup(grp.id); + if (!existinggrp) + continue; + existinggrp->health = grp.health; + } + } + else + { + TMap::ConstIterator it(level.healthGroups); + TMap::ConstPair* pair; + while (it.NextPair(pair)) + { + FHealthGroup grp = pair->Value; + arc(nullptr, grp); + } + } + + arc.EndArray(); + } +} \ No newline at end of file diff --git a/src/p_destructible.h b/src/p_destructible.h new file mode 100755 index 000000000..c9c5e81ab --- /dev/null +++ b/src/p_destructible.h @@ -0,0 +1,39 @@ +#pragma once + +#include "tarray.h" +#include "r_defs.h" +#include "p_trace.h" + +// [ZZ] Destructible geometry related +struct FHealthGroup +{ + TArray sectors; + TArray lines; + int health; + int id; +}; + +// for P_DamageSector +enum +{ + SECPART_Floor = 0, + SECPART_Ceiling = 1 +}; + +void P_InitHealthGroups(); + +void P_SetHealthGroupHealth(int group, int health); + +FHealthGroup* P_GetHealthGroup(int id); +FHealthGroup* P_GetHealthGroupOrNew(int id, int startinghealth); + +void P_DamageSector(sector_t* sector, AActor* source, int damage, FName damagetype, int part, DVector3 position, bool dogroups = true); +void P_DamageLinedef(line_t* line, AActor* source, int damage, FName damagetype, int side, DVector3 position, bool dogroups = true); + +void P_GeometryLineAttack(FTraceResults& trace, AActor* thing, int damage, FName damageType); +void P_GeometryRadiusAttack(AActor* bombspot, AActor* bombsource, int bombdamage, int bombdistance, FName damagetype, int fulldamagedistance); + +bool P_CheckLinedefVulnerable(line_t* line, int side, int part = -1); +bool P_CheckSectorVulnerable(sector_t* sector, int part); + +void P_SerializeHealthGroups(FSerializer& arc); \ No newline at end of file diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index 67aa64eef..a1730c7ce 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -711,7 +711,7 @@ bool P_Move (AActor *actor) { actor->floorsector->TriggerSectorActions(actor, SECSPAC_HitFloor); } - P_CheckFor3DFloorHit(actor, actor->Z()); + P_CheckFor3DFloorHit(actor, actor->Z(), true); } } } diff --git a/src/p_floor.cpp b/src/p_floor.cpp index d0b6cb077..7141a842e 100644 --- a/src/p_floor.cpp +++ b/src/p_floor.cpp @@ -229,7 +229,7 @@ void DFloor::SetFloorChangeType (sector_t *sec, int change) switch (change & 3) { case 1: - m_NewSpecial.Clear(); + m_NewSpecial = {}; m_Type = DFloor::genFloorChg0; break; case 2: @@ -828,7 +828,7 @@ bool EV_DoDonut (int tag, line_t *line, double pillarspeed, double slimespeed) floor->m_Speed = slimespeed; floor->m_Instant = false; floor->m_Texture = s3->GetTexture(sector_t::floor); - floor->m_NewSpecial.Clear(); + floor->m_NewSpecial = {}; height = s3->FindHighestFloorPoint (&spot); floor->m_FloorDestDist = s2->floorplane.PointToDist (spot, height); floor->StartFloorSound (); diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index ab7dce496..2d7d6026c 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -112,30 +112,34 @@ void P_TouchSpecialThing (AActor *special, AActor *toucher) // [RH] -// SexMessage: Replace parts of strings with gender-specific pronouns +// PronounMessage: Replace parts of strings with player-specific pronouns // // The following expansions are performed: -// %g -> he/she/it -// %h -> him/her/it -// %p -> his/her/its +// %g -> he/she/they/it +// %h -> him/her/them/it +// %p -> his/her/their/its +// %s -> his/hers/theirs/its +// %r -> he's/she's/they're/it's // %o -> other (victim) // %k -> killer // -void SexMessage (const char *from, char *to, int gender, const char *victim, const char *killer) +void PronounMessage (const char *from, char *to, int pronoun, const char *victim, const char *killer) { - static const char *genderstuff[3][3] = + static const char *pronouns[GENDER_MAX][5] = { - { "he", "him", "his" }, - { "she", "her", "her" }, - { "it", "it", "its" } + { "he", "him", "his", "his", "he's" }, + { "she", "her", "her", "hers", "she's" }, + { "they", "them", "their", "theirs", "they're" }, + { "it", "it", "its", "its'", "it's" } }; - static const int gendershift[3][3] = + static const int pronounshift[GENDER_MAX][5] = { - { 2, 3, 3 }, - { 3, 3, 3 }, - { 2, 2, 3 } + { 2, 3, 3, 3, 4 }, + { 3, 3, 3, 4, 5 }, + { 4, 4, 5, 6, 7 }, + { 2, 2, 3, 4, 4 } }; - const char *subst = NULL; + const char *substitute = NULL; do { @@ -145,32 +149,34 @@ void SexMessage (const char *from, char *to, int gender, const char *victim, con } else { - int gendermsg = -1; + int grammarcase = -1; switch (from[1]) { - case 'g': gendermsg = 0; break; - case 'h': gendermsg = 1; break; - case 'p': gendermsg = 2; break; - case 'o': subst = victim; break; - case 'k': subst = killer; break; + case 'g': grammarcase = 0; break; // Subject + case 'h': grammarcase = 1; break; // Object + case 'p': grammarcase = 2; break; // Possessive Determiner + case 's': grammarcase = 3; break; // Possessive Pronoun + case 'r': grammarcase = 4; break; // Perfective + case 'o': substitute = victim; break; + case 'k': substitute = killer; break; } - if (subst != NULL) + if (substitute != nullptr) { - size_t len = strlen (subst); - memcpy (to, subst, len); + size_t len = strlen (substitute); + memcpy (to, substitute, len); to += len; from++; - subst = NULL; + substitute = nullptr; } - else if (gendermsg < 0) + else if (grammarcase < 0) { *to++ = '%'; } else { - strcpy (to, genderstuff[gender][gendermsg]); - to += gendershift[gender][gendermsg]; + strcpy (to, pronouns[pronoun][grammarcase]); + to += pronounshift[pronoun][grammarcase]; from++; } } @@ -268,7 +274,7 @@ void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgf if (message == NULL || strlen(message) <= 0) return; - SexMessage (message, gendermessage, self->player->userinfo.GetGender(), + PronounMessage (message, gendermessage, self->player->userinfo.GetGender(), self->player->userinfo.GetName(), attacker->player->userinfo.GetName()); Printf (PRINT_MEDIUM, "%s\n", gendermessage); } @@ -402,7 +408,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf player->fragcount--; if (deathmatch && player->spreecount >= 5 && cl_showsprees) { - SexMessage (GStrings("SPREEKILLSELF"), buff, + PronounMessage (GStrings("SPREEKILLSELF"), buff, player->userinfo.GetGender(), player->userinfo.GetName(), player->userinfo.GetName()); StatusBar->AttachMessage (Create(SmallFont, buff, @@ -460,7 +466,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf { if (!AnnounceSpreeLoss (this)) { - SexMessage (GStrings("SPREEOVER"), buff, player->userinfo.GetGender(), + PronounMessage (GStrings("SPREEOVER"), buff, player->userinfo.GetGender(), player->userinfo.GetName(), source->player->userinfo.GetName()); StatusBar->AttachMessage (Create (SmallFont, buff, 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); @@ -470,7 +476,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf { if (!AnnounceSpree (source)) { - SexMessage (spreemsg, buff, player->userinfo.GetGender(), + PronounMessage (spreemsg, buff, player->userinfo.GetGender(), player->userinfo.GetName(), source->player->userinfo.GetName()); StatusBar->AttachMessage (Create (SmallFont, buff, 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); @@ -520,7 +526,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf if (!AnnounceMultikill (source)) { - SexMessage (multimsg, buff, player->userinfo.GetGender(), + PronounMessage (multimsg, buff, player->userinfo.GetGender(), player->userinfo.GetName(), source->player->userinfo.GetName()); StatusBar->AttachMessage (Create (SmallFont, buff, 1.5f, 0.8f, 0, 0, CR_RED, 3.f, 0.5f), MAKE_ID('M','K','I','L')); @@ -1577,7 +1583,7 @@ dopain: } } //ALLOWPAIN and CAUSEPAIN can still trigger infighting, even if no pain state is worked out. - target->reactiontime = 0; // we're awake now... + if (target->player == nullptr) target->reactiontime = 0; // we're awake now... if (source) { if (source == target->target) diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 253f505e4..081c7f400 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -56,6 +56,7 @@ #include "p_spec.h" #include "g_levellocals.h" #include "vm.h" +#include "p_destructible.h" // Remaps EE sector change types to Generic_Floor values. According to the Eternity Wiki: /* @@ -3478,6 +3479,53 @@ FUNC(LS_Sector_SetCeilingGlow) return true; } +FUNC(LS_Line_SetHealth) +// Line_SetHealth(id, health) +{ + FLineIdIterator itr(arg0); + int l; + + if (arg1 < 0) + arg1 = 0; + + while ((l = itr.Next()) >= 0) + { + line_t* line = &level.lines[l]; + line->health = arg1; + if (line->healthgroup) + P_SetHealthGroupHealth(line->healthgroup, arg1); + } + return true; +} + +FUNC(LS_Sector_SetHealth) +// Sector_SetHealth(id, part, health) +{ + FSectorTagIterator itr(arg0); + int s; + + if (arg2 < 0) + arg2 = 0; + + while ((s = itr.Next()) >= 0) + { + sector_t* sector = &level.sectors[s]; + if (arg1 == SECPART_Ceiling) + { + sector->healthceiling = arg2; + if (sector->healthceilinggroup) + P_SetHealthGroupHealth(sector->healthceilinggroup, arg2); + } + else if (arg1 == SECPART_Floor) + { + sector->healthfloor = arg2; + if (sector->healthfloorgroup) + P_SetHealthGroupHealth(sector->healthfloorgroup, arg2); + } + } + return true; +} + static lnSpecFunc LineSpecials[] = { /* 0 */ LS_NOP, @@ -3630,8 +3678,8 @@ static lnSpecFunc LineSpecials[] = /* 147 */ LS_NOP, /* 148 */ LS_NOP, /* 149 */ LS_NOP, - /* 150 */ LS_NOP, - /* 151 */ LS_NOP, + /* 150 */ LS_Line_SetHealth, + /* 151 */ LS_Sector_SetHealth, /* 152 */ LS_NOP, // 152 Team_Score /* 153 */ LS_NOP, // 153 Team_GivePoints /* 154 */ LS_Teleport_NoStop, diff --git a/src/p_local.h b/src/p_local.h index d1a81a992..e553def29 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -248,7 +248,6 @@ AActor *P_RoughMonsterSearch (AActor *mo, int distance, bool onlyseekable=false, // P_MAP // - struct spechit_t { line_t *line; diff --git a/src/p_map.cpp b/src/p_map.cpp index 58c0bb706..bb7164ef7 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -3649,22 +3649,6 @@ bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop) mo->Angles.Yaw = angle; mo->VelFromAngle(speed); mo->PlayBounceSound(true); - if (mo->BounceFlags & BOUNCE_UseBounceState) - { - FName names[] = { NAME_Bounce, NAME_Actor, NAME_Creature }; - FState *bouncestate; - int count = 2; - - if ((BlockingMobj->flags & MF_SHOOTABLE) && !(BlockingMobj->flags & MF_NOBLOOD)) - { - count = 3; - } - bouncestate = mo->FindState(count, names); - if (bouncestate != NULL) - { - mo->SetState(bouncestate); - } - } } else { @@ -3703,6 +3687,21 @@ bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop) mo->BounceFlags &= ~BOUNCE_TypeMask; } } + if (mo->BounceFlags & BOUNCE_UseBounceState) + { + FName names[] = { NAME_Bounce, NAME_Actor, NAME_Creature }; + FState *bouncestate; + int count = 2; + if ((BlockingMobj->flags & MF_SHOOTABLE) && !(BlockingMobj->flags & MF_NOBLOOD)) + { + count = 3; + } + bouncestate = mo->FindState(count, names); + if (bouncestate != NULL) + { + mo->SetState(bouncestate); + } + } return true; } return false; @@ -4684,6 +4683,8 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance, { if (trace.HitType != TRACE_HitActor) { + P_GeometryLineAttack(trace, t1, damage, damageType); + // position a bit closer for puffs if (nointeract || trace.HitType != TRACE_HitWall || ((trace.Line->special != Line_Horizon) || spawnSky)) { @@ -5517,6 +5518,8 @@ void P_RailAttack(FRailParams *p) } } + P_GeometryLineAttack(trace, p->source, p->damage, damagetype); + // Spawn a decal or puff at the point where the trace ended. if (trace.HitType == TRACE_HitWall) { @@ -6127,6 +6130,8 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bom bombsource = bombspot; } + P_GeometryRadiusAttack(bombspot, bombsource, bombdamage, bombdistance, bombmod, fulldamagedistance); + int count = 0; while ((it.Next(&cres))) { diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index bd8da579f..fe40348c1 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -296,6 +296,8 @@ DEFINE_FIELD(AActor, lastbump) DEFINE_FIELD(AActor, DesignatedTeam) DEFINE_FIELD(AActor, BlockingMobj) DEFINE_FIELD(AActor, BlockingLine) +DEFINE_FIELD(AActor, BlockingCeiling) +DEFINE_FIELD(AActor, BlockingFloor) DEFINE_FIELD(AActor, PoisonDamage) DEFINE_FIELD(AActor, PoisonDamageType) DEFINE_FIELD(AActor, PoisonDuration) @@ -478,6 +480,8 @@ void AActor::Serialize(FSerializer &arc) A("smokecounter", smokecounter) ("blockingmobj", BlockingMobj) A("blockingline", BlockingLine) + A("blockingceiling", BlockingCeiling) + A("blockingfloor", BlockingFloor) A("visibletoteam", VisibleToTeam) A("pushfactor", pushfactor) A("species", Species) @@ -1938,6 +1942,31 @@ bool AActor::Massacre () void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target, bool onsky) { + // [ZZ] line damage callback + if (line) + { + int wside = P_PointOnLineSide(mo->Pos(), line); + int oside = !wside; + side_t* otherside = line->sidedef[oside]; + // check if hit upper or lower part + if (otherside) + { + sector_t* othersector = otherside->sector; + double otherfloorz = othersector->floorplane.ZatPoint(mo->Pos()); + double otherceilingz = othersector->ceilingplane.ZatPoint(mo->Pos()); + double actualz = mo->Pos().Z; + if (actualz < otherfloorz && othersector->healthfloor > 0 && P_CheckLinedefVulnerable(line, wside, SECPART_Floor)) + P_DamageSector(othersector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Floor, mo->Pos()); + if (actualz > otherceilingz && othersector->healthceiling > 0 && P_CheckLinedefVulnerable(line, wside, SECPART_Ceiling)) + P_DamageSector(othersector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Ceiling, mo->Pos()); + } + + if (line->health > 0 && P_CheckLinedefVulnerable(line, wside)) + { + P_DamageLinedef(line, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, wside, mo->Pos()); + } + } + if (mo->flags3 & MF3_EXPLOCOUNT) { if (++mo->threshold < mo->DefThreshold) @@ -2527,6 +2556,21 @@ double P_XYMovement (AActor *mo, DVector2 scroll) AActor *BlockingMobj = mo->BlockingMobj; line_t *BlockingLine = mo->BlockingLine; + // [ZZ] + if (!BlockingLine && !BlockingMobj) // hit floor or ceiling while XY movement - sector actions + { + int hitpart = -1; + sector_t* hitsector = nullptr; + secplane_t* hitplane = nullptr; + if (tm.ceilingsector && mo->Z() + mo->Height > tm.ceilingsector->ceilingplane.ZatPoint(tm.pos.XY())) + mo->BlockingCeiling = tm.ceilingsector; + if (tm.floorsector && mo->Z() < tm.floorsector->floorplane.ZatPoint(tm.pos.XY())) + mo->BlockingFloor = tm.floorsector; + // the following two only set the appropriate field - to avoid issues caused by running actions right in the middle of XY movement + P_CheckFor3DFloorHit(mo, mo->floorz, false); + P_CheckFor3DCeilingHit(mo, mo->ceilingz, false); + } + if (!(mo->flags & MF_MISSILE) && (mo->BounceFlags & BOUNCE_MBF) && (BlockingMobj != NULL ? P_BounceActor(mo, BlockingMobj, false) : P_BounceWall(mo))) { @@ -2689,30 +2733,40 @@ double P_XYMovement (AActor *mo, DVector2 scroll) explode: // explode a missile bool onsky = false; - if (tm.ceilingline && - tm.ceilingline->backsector && - tm.ceilingline->backsector->GetTexture(sector_t::ceiling) == skyflatnum && - mo->Z() >= tm.ceilingline->backsector->ceilingplane.ZatPoint(mo->PosRelative(tm.ceilingline))) + if (tm.ceilingline && + tm.ceilingline->backsector && + tm.ceilingline->backsector->GetTexture(sector_t::ceiling) == skyflatnum && + mo->Z() >= tm.ceilingline->backsector->ceilingplane.ZatPoint(mo->PosRelative(tm.ceilingline))) + { + if (!(mo->flags3 & MF3_SKYEXPLODE)) { - if (!(mo->flags3 & MF3_SKYEXPLODE)) - { - // Hack to prevent missiles exploding against the sky. - // Does not handle sky floors. - mo->Destroy(); - return Oldfloorz; - } - else onsky = true; + // Hack to prevent missiles exploding against the sky. + // Does not handle sky floors. + mo->Destroy(); + return Oldfloorz; } - // [RH] Don't explode on horizon lines. - if (mo->BlockingLine != NULL && mo->BlockingLine->special == Line_Horizon) + else onsky = true; + } + // [RH] Don't explode on horizon lines. + if (mo->BlockingLine != NULL && mo->BlockingLine->special == Line_Horizon) + { + if (!(mo->flags3 & MF3_SKYEXPLODE)) { - if (!(mo->flags3 & MF3_SKYEXPLODE)) - { - mo->Destroy(); - return Oldfloorz; - } - else onsky = true; + mo->Destroy(); + return Oldfloorz; } + else onsky = true; + } + if (mo->BlockingCeiling) // hit floor or ceiling while XY movement + { + if (mo->BlockingCeiling->healthceiling > 0 && P_CheckSectorVulnerable(mo->BlockingCeiling, SECPART_Ceiling)) + P_DamageSector(mo->BlockingCeiling, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Ceiling, mo->Pos()); + } + if (mo->BlockingFloor) + { + if (mo->BlockingFloor->healthfloor > 0 && P_CheckSectorVulnerable(mo->BlockingFloor, SECPART_Floor)) + P_DamageSector(mo->BlockingFloor, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Floor, mo->Pos()); + } P_ExplodeMissile (mo, mo->BlockingLine, BlockingMobj, onsky); return Oldfloorz; } @@ -3065,13 +3119,14 @@ void P_ZMovement (AActor *mo, double oldfloorz) mo->Sector->SecActTarget != NULL && mo->Sector->floorplane.ZatPoint(mo) == mo->floorz) { // [RH] Let the sector do something to the actor - mo->Sector->TriggerSectorActions (mo, SECSPAC_HitFloor); + mo->Sector->TriggerSectorActions(mo, SECSPAC_HitFloor); } - P_CheckFor3DFloorHit(mo, mo->floorz); + P_CheckFor3DFloorHit(mo, mo->floorz, true); // [RH] Need to recheck this because the sector action might have // teleported the actor so it is no longer below the floor. if (mo->Z() <= mo->floorz) { + mo->BlockingFloor = mo->Sector; if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP)) { mo->SetZ(mo->floorz); @@ -3105,6 +3160,9 @@ void P_ZMovement (AActor *mo, double oldfloorz) else onsky = true; } P_HitFloor (mo); + // hit floor: direct damage callback + if (mo->Sector->healthfloor > 0 && P_CheckSectorVulnerable(mo->Sector, SECPART_Floor)) + P_DamageSector(mo->Sector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Floor, mo->Pos()); P_ExplodeMissile (mo, NULL, NULL, onsky); return; } @@ -3175,11 +3233,12 @@ void P_ZMovement (AActor *mo, double oldfloorz) { // [RH] Let the sector do something to the actor mo->Sector->TriggerSectorActions (mo, SECSPAC_HitCeiling); } - P_CheckFor3DCeilingHit(mo, mo->ceilingz); + P_CheckFor3DCeilingHit(mo, mo->ceilingz, true); // [RH] Need to recheck this because the sector action might have // teleported the actor so it is no longer above the ceiling. if (mo->Top() > mo->ceilingz) { + mo->BlockingCeiling = mo->Sector; mo->SetZ(mo->ceilingz - mo->Height); if (mo->BounceFlags & BOUNCE_Ceilings) { // ceiling bounce @@ -3208,6 +3267,9 @@ void P_ZMovement (AActor *mo, double oldfloorz) } else onsky = true; } + // hit ceiling: direct damage callback + if (mo->Sector->healthceiling > 0 && P_CheckSectorVulnerable(mo->Sector, SECPART_Ceiling)) + P_DamageSector(mo->Sector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Ceiling, mo->Pos()); P_ExplodeMissile (mo, NULL, NULL, onsky); return; } @@ -4454,12 +4516,21 @@ void AActor::Tick () } // Handle X and Y velocities - BlockingMobj = NULL; + BlockingMobj = nullptr; + sector_t* oldBlockingCeiling = BlockingCeiling; + sector_t* oldBlockingFloor = BlockingFloor; + BlockingFloor = nullptr; + BlockingCeiling = nullptr; double oldfloorz = P_XYMovement (this, cumm); if (ObjectFlags & OF_EuthanizeMe) { // actor was destroyed return; } + // [ZZ] trigger hit floor/hit ceiling actions from XY movement + if (BlockingFloor && BlockingFloor != oldBlockingFloor && (!player || !(player->cheats & CF_PREDICTING)) && BlockingFloor->SecActTarget) + BlockingFloor->TriggerSectorActions(this, SECSPAC_HitFloor); + if (BlockingCeiling && BlockingCeiling != oldBlockingCeiling && (!player || !(player->cheats & CF_PREDICTING)) && BlockingCeiling->SecActTarget) + BlockingCeiling->TriggerSectorActions(this, SECSPAC_HitCeiling); if (Vel.X == 0 && Vel.Y == 0) // Actors at rest { if (flags2 & MF2_BLASTED) @@ -4705,11 +4776,11 @@ void AActor::CheckSectorTransition(sector_t *oldsec) } if (Z() == floorz) { - P_CheckFor3DFloorHit(this, Z()); + P_CheckFor3DFloorHit(this, Z(), true); } if (Top() == ceilingz) { - P_CheckFor3DCeilingHit(this, Top()); + P_CheckFor3DCeilingHit(this, Top(), true); } } } diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 6248dab3e..93b583c58 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -56,6 +56,7 @@ #include "serializer.h" #include "g_levellocals.h" #include "events.h" +#include "p_destructible.h" //========================================================================== // @@ -74,6 +75,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, line_t &line, line_t * .Args("args", line.args, def->args, line.special) ("portalindex", line.portalindex, def->portalindex) ("locknumber", line.locknumber, def->locknumber) + ("health", line.health, def->health) // Unless the map loader is changed the sidedef references will not change between map loads so there's no need to save them. //.Array("sides", line.sidedef, 2) .EndObject(); @@ -293,6 +295,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, sector_t &p, sector_t .Terrain("floorterrain", p.terrainnum[0], &def->terrainnum[0]) .Terrain("ceilingterrain", p.terrainnum[1], &def->terrainnum[1]) ("scrolls", scroll, nul) + ("healthfloor", p.healthfloor, def->healthfloor) + ("healthceiling", p.healthceiling, def->healthceiling) // GZDoom exclusive: .Array("reflect", p.reflect, def->reflect, 2, true) .EndObject(); @@ -991,6 +995,8 @@ void G_SerializeLevel(FSerializer &arc, bool hubload) arc("sectorportals", level.sectorPortals); if (arc.isReading()) P_FinalizePortals(); + // [ZZ] serialize health groups + P_SerializeHealthGroups(arc); // [ZZ] serialize events E_SerializeEvents(arc); DThinker::SerializeThinkers(arc, hubload); diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index 831062fc0..be86c87ef 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -2652,6 +2652,10 @@ DEFINE_FIELD_X(Sector, sector_t, damageamount) DEFINE_FIELD_X(Sector, sector_t, damageinterval) DEFINE_FIELD_X(Sector, sector_t, leakydamage) DEFINE_FIELD_X(Sector, sector_t, ZoneNumber) +DEFINE_FIELD_X(Sector, sector_t, healthceiling) +DEFINE_FIELD_X(Sector, sector_t, healthfloor) +DEFINE_FIELD_X(Sector, sector_t, healthceilinggroup) +DEFINE_FIELD_X(Sector, sector_t, healthfloorgroup) DEFINE_FIELD_X(Sector, sector_t, MoreFlags) DEFINE_FIELD_X(Sector, sector_t, Flags) DEFINE_FIELD_X(Sector, sector_t, SecActTarget) @@ -2675,6 +2679,8 @@ DEFINE_FIELD_X(Line, line_t, validcount) DEFINE_FIELD_X(Line, line_t, locknumber) DEFINE_FIELD_X(Line, line_t, portalindex) DEFINE_FIELD_X(Line, line_t, portaltransferred) +DEFINE_FIELD_X(Line, line_t, health) +DEFINE_FIELD_X(Line, line_t, healthgroup) DEFINE_FIELD_X(Side, side_t, sector) DEFINE_FIELD_X(Side, side_t, linedef) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index db5baf518..4a48afbb6 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -101,9 +101,11 @@ #include "edata.h" #endif #include "events.h" +#include "p_destructible.h" #include "types.h" #include "i_time.h" #include "scripting/vm/vm.h" +#include "hwrenderer/data/flatvertices.h" #include "fragglescript/t_fs.h" @@ -3641,7 +3643,7 @@ void P_FreeExtraLevelData() // //=========================================================================== -void P_SetupLevel (const char *lumpname, int position) +void P_SetupLevel (const char *lumpname, int position, bool newGame) { cycle_t times[20]; #if 0 @@ -3719,6 +3721,11 @@ void P_SetupLevel (const char *lumpname, int position) level.lumpnum = map->lumpnum; hasglnodes = false; + if (newGame) + { + E_NewGame(EventHandlerType::PerMap); + } + // [RH] Support loading Build maps (because I felt like it. :-) buildmap = false; #if 0 @@ -4115,9 +4122,10 @@ void P_SetupLevel (const char *lumpname, int position) // This must be done BEFORE the PolyObj Spawn!!! InitRenderInfo(); // create hardware independent renderer resources for the level. - screen->InitForLevel(); // create hardware dependent level resources (e.g. the vertex buffer) + screen->mVertexData->CreateVBO(); SWRenderer->SetColormap(); //The SW renderer needs to do some special setup for the level's default colormap. InitPortalGroups(); + P_InitHealthGroups(); times[16].Clock(); if (reloop) P_LoopSidedefs (false); @@ -4297,13 +4305,13 @@ void P_Init () } static void P_Shutdown () -{ - // [ZZ] delete global event handlers - DThinker::DestroyThinkersInList(STAT_STATIC); - E_Shutdown(false); +{ + DThinker::DestroyThinkersInList(STAT_STATIC); P_DeinitKeyMessages (); P_FreeLevelData (); P_FreeExtraLevelData (); + // [ZZ] delete global event handlers + E_Shutdown(false); ST_Clear(); FS_Close(); for (auto &p : players) diff --git a/src/p_setup.h b/src/p_setup.h index b9c680632..31612894b 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -138,7 +138,7 @@ bool P_CheckMapData(const char * mapname); // [RH] The only parameter used is mapname, so I removed playermask and skill. // On September 1, 1998, I added the position to indicate which set // of single-player start spots should be spawned in the level. -void P_SetupLevel (const char *mapname, int position); +void P_SetupLevel (const char *mapname, int position, bool newGame); void P_FreeLevelData(); void P_FreeExtraLevelData(); diff --git a/src/p_spec.h b/src/p_spec.h index 6dc118658..61cc7b2f0 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -439,7 +439,7 @@ protected: // [RH] Need these for BOOM-ish transferring ceilings FTextureID m_Texture; - secspecial_t m_NewSpecial; + secspecial_t m_NewSpecial{}; // ID int m_Tag; @@ -536,7 +536,7 @@ public: bool m_Hexencrush; bool m_Instant; int m_Direction; - secspecial_t m_NewSpecial; + secspecial_t m_NewSpecial{}; FTextureID m_Texture; double m_FloorDestDist; double m_Speed; diff --git a/src/p_things.cpp b/src/p_things.cpp index 4012a5872..9fac1cd67 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -163,6 +163,85 @@ bool P_Thing_Move (int tid, AActor *source, int mapspot, bool fog) return false; } +// [MC] Was part of P_Thing_Projectile, now its own function for use in ZScript. +// Aims mobj at targ based on speed and targ's velocity. +void VelIntercept(AActor *targ, AActor *mobj, double speed, bool aimpitch = false, bool oldvel = false) +{ + if (targ == nullptr || mobj == nullptr) return; + + if (speed > 0 && !targ->Vel.isZero()) + { + DVector3 aim = mobj->Vec3To(targ); + aim.Z += targ->Height / 2; + // Aiming at the target's position some time in the future + // is basically just an application of the law of sines: + // a/sin(A) = b/sin(B) + // Thanks to all those on the notgod phorum for helping me + // with the math. I don't think I would have thought of using + // trig alone had I been left to solve it by myself. + + bool nolead = false; + DVector3 tvel = targ->Vel; + if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3) + { // If the target is subject to gravity and not underwater, + // assume that it isn't moving vertically. Thanks to gravity, + // even if we did consider the vertical component of the target's + // velocity, we would still miss more often than not. + tvel.Z = 0.0; + nolead = !!(targ->Vel.X == 0 && targ->Vel.Y == 0); + } + if (!nolead) + { + double dist = aim.Length(); + double targspeed = tvel.Length(); + double ydotx = -aim | tvel; + double a = g_acos(clamp(ydotx / targspeed / dist, -1.0, 1.0)); + double multiplier = double(pr_leadtarget.Random2())*0.1 / 255 + 1.1; + double sinb = -clamp(targspeed*multiplier * g_sin(a) / speed, -1.0, 1.0); + DVector3 prevel = mobj->Vel; + // Use the cross product of two of the triangle's sides to get a + // rotation vector. + DVector3 rv(tvel ^ aim); + // The vector must be normalized. + rv.MakeUnit(); + // Now combine the rotation vector with angle b to get a rotation matrix. + DMatrix3x3 rm(rv, g_cos(g_asin(sinb)), sinb); + // And multiply the original aim vector with the matrix to get a + // new aim vector that leads the target. + DVector3 aimvec = rm * aim; + // And make the projectile follow that vector at the desired speed. + mobj->Vel = aimvec * (speed / dist); + mobj->AngleFromVel(); + if (oldvel) + { + mobj->Vel = prevel; + } + if (aimpitch) // [MC] Ripped right out of A_FaceMovementDirection + { + const DVector2 velocity = mobj->Vel.XY(); + mobj->Angles.Pitch = -VecToAngle(velocity.Length(), mobj->Vel.Z); + } + } + else + { + mobj->Angles.Yaw = mobj->AngleTo(targ); + mobj->Vel = aim.Resized(speed); + } + } +} + +DEFINE_ACTION_FUNCTION(AActor, VelIntercept) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT_NOT_NULL(targ, AActor); + PARAM_FLOAT_DEF(speed); + PARAM_BOOL_DEF(aimpitch); + PARAM_BOOL_DEF(oldvel); + if (speed < 0) speed = self->Speed; + VelIntercept(targ, self, speed, aimpitch, oldvel); + return 0; +} + bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_name, DAngle angle, double speed, double vspeed, int dest, AActor *forcedest, int gravity, int newtid, bool leadTarget) @@ -244,58 +323,11 @@ bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_nam } mobj->target = spot; - if (targ != NULL) + if (targ != nullptr) { - DVector3 aim = mobj->Vec3To(targ); - aim.Z += targ->Height / 2; - - if (leadTarget && speed > 0 && !targ->Vel.isZero()) + if (leadTarget) { - // Aiming at the target's position some time in the future - // is basically just an application of the law of sines: - // a/sin(A) = b/sin(B) - // Thanks to all those on the notgod phorum for helping me - // with the math. I don't think I would have thought of using - // trig alone had I been left to solve it by myself. - - DVector3 tvel = targ->Vel; - if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3) - { // If the target is subject to gravity and not underwater, - // assume that it isn't moving vertically. Thanks to gravity, - // even if we did consider the vertical component of the target's - // velocity, we would still miss more often than not. - tvel.Z = 0.0; - if (targ->Vel.X == 0 && targ->Vel.Y == 0) - { - goto nolead; - } - } - double dist = aim.Length(); - double targspeed = tvel.Length(); - double ydotx = -aim | tvel; - double a = g_acos (clamp (ydotx / targspeed / dist, -1.0, 1.0)); - double multiplier = double(pr_leadtarget.Random2())*0.1/255+1.1; - double sinb = -clamp (targspeed*multiplier * g_sin(a) / speed, -1.0, 1.0); - - // Use the cross product of two of the triangle's sides to get a - // rotation vector. - DVector3 rv(tvel ^ aim); - // The vector must be normalized. - rv.MakeUnit(); - // Now combine the rotation vector with angle b to get a rotation matrix. - DMatrix3x3 rm(rv, g_cos(g_asin(sinb)), sinb); - // And multiply the original aim vector with the matrix to get a - // new aim vector that leads the target. - DVector3 aimvec = rm * aim; - // And make the projectile follow that vector at the desired speed. - mobj->Vel = aimvec * (speed / dist); - mobj->AngleFromVel(); - } - else - { -nolead: - mobj->Angles.Yaw = mobj->AngleTo(targ); - mobj->Vel = aim.Resized (speed); + VelIntercept(targ, mobj, speed); } if (mobj->flags2 & MF2_SEEKERMISSILE) { diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index a2b45fe7f..f8b6d2c4d 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -1120,6 +1120,21 @@ public: tagstring = CheckString(key); break; + case NAME_Health: + ld->health = CheckInt(key); + break; + + case NAME_DamageSpecial: + Flag(ld->activation, SPAC_Damage, key); + break; + + case NAME_DeathSpecial: + Flag(ld->activation, SPAC_Death, key); + break; + + case NAME_HealthGroup: + ld->healthgroup = CheckInt(key); + break; default: break; @@ -1362,7 +1377,6 @@ public: double scroll_floor_y = 0; FName scroll_floor_type = NAME_None; - memset(sec, 0, sizeof(*sec)); sec->lightlevel = 160; sec->SetXScale(sector_t::floor, 1.); // [RH] floor and ceiling scaling @@ -1675,19 +1689,19 @@ public: break; case NAME_floorglowcolor: - sec->planes[sector_t::floor].GlowColor = CheckInt(key); + sec->SetGlowColor(sector_t::floor, CheckInt(key)); break; case NAME_floorglowheight: - sec->planes[sector_t::floor].GlowHeight = (float)CheckFloat(key); + sec->SetGlowHeight(sector_t::floor, (float)CheckFloat(key)); break; case NAME_ceilingglowcolor: - sec->planes[sector_t::ceiling].GlowColor = CheckInt(key); + sec->SetGlowColor(sector_t::ceiling, CheckInt(key)); break; case NAME_ceilingglowheight: - sec->planes[sector_t::ceiling].GlowHeight = (float)CheckFloat(key); + sec->SetGlowHeight(sector_t::ceiling, (float)CheckFloat(key)); break; case NAME_Noattack: @@ -1769,6 +1783,22 @@ public: // These two are used by Eternity for something I do not understand. //case NAME_portal_ceil_useglobaltex: //case NAME_portal_floor_useglobaltex: + + case NAME_HealthFloor: + sec->healthfloor = CheckInt(key); + break; + + case NAME_HealthCeiling: + sec->healthceiling = CheckInt(key); + break; + + case NAME_HealthFloorGroup: + sec->healthfloorgroup = CheckInt(key); + break; + + case NAME_HealthCeilingGroup: + sec->healthceilinggroup = CheckInt(key); + break; default: break; diff --git a/src/polyrenderer/scene/poly_cull.cpp b/src/polyrenderer/scene/poly_cull.cpp index 4bda49b7c..6668e2f2b 100644 --- a/src/polyrenderer/scene/poly_cull.cpp +++ b/src/polyrenderer/scene/poly_cull.cpp @@ -164,7 +164,7 @@ void PolyCull::CullSubsector(subsector_t *sub) angle_t angle2 = PointToPseudoAngle(line->v1->fX(), line->v1->fY()); angle_t angle1 = PointToPseudoAngle(line->v2->fX(), line->v2->fY()); bool lineVisible = !IsSegmentCulled(angle1, angle2); - if (lineVisible && line->backsector == nullptr) + if (lineVisible && IsSolidLine(line)) { MarkSegmentCulled(angle1, angle2); } @@ -182,6 +182,52 @@ void PolyCull::CullSubsector(subsector_t *sub) SubsectorDepths[sub->Index()] = subsectorDepth; } +bool PolyCull::IsSolidLine(seg_t *line) +{ + // One-sided + if (!line->backsector) return true; + + // Portal + if (line->linedef && line->linedef->isVisualPortal() && line->sidedef == line->linedef->sidedef[0]) return true; + + double frontCeilingZ1 = line->frontsector->ceilingplane.ZatPoint(line->v1); + double frontFloorZ1 = line->frontsector->floorplane.ZatPoint(line->v1); + double frontCeilingZ2 = line->frontsector->ceilingplane.ZatPoint(line->v2); + double frontFloorZ2 = line->frontsector->floorplane.ZatPoint(line->v2); + + double backCeilingZ1 = line->backsector->ceilingplane.ZatPoint(line->v1); + double backFloorZ1 = line->backsector->floorplane.ZatPoint(line->v1); + double backCeilingZ2 = line->backsector->ceilingplane.ZatPoint(line->v2); + double backFloorZ2 = line->backsector->floorplane.ZatPoint(line->v2); + + // Closed door. + if (backCeilingZ1 <= frontFloorZ1 && backCeilingZ2 <= frontFloorZ2) return true; + if (backFloorZ1 >= frontCeilingZ1 && backFloorZ2 >= frontCeilingZ2) return true; + + // properly render skies (consider door "open" if both ceilings are sky) + if (line->backsector->GetTexture(sector_t::ceiling) == skyflatnum && line->frontsector->GetTexture(sector_t::ceiling) == skyflatnum) return false; + + // if door is closed because back is shut: + if (!(backCeilingZ1 <= backFloorZ1 && backCeilingZ2 <= backFloorZ2)) return false; + + // preserve a kind of transparent door/lift special effect: + if (((backCeilingZ1 >= frontCeilingZ1 && backCeilingZ2 >= frontCeilingZ2) || line->sidedef->GetTexture(side_t::top).isValid()) + && ((backFloorZ1 <= frontFloorZ1 && backFloorZ2 <= frontFloorZ2) || line->sidedef->GetTexture(side_t::bottom).isValid())) + { + // killough 1/18/98 -- This function is used to fix the automap bug which + // showed lines behind closed doors simply because the door had a dropoff. + // + // It assumes that Doom has already ruled out a door being closed because + // of front-back closure (e.g. front floor is taller than back ceiling). + + // This fixes the automap floor height bug -- killough 1/18/98: + // killough 4/7/98: optimize: save result in doorclosed for use in r_segs.c + return true; + } + + return false; +} + bool PolyCull::IsSegmentCulled(angle_t startAngle, angle_t endAngle) const { if (startAngle > endAngle) diff --git a/src/polyrenderer/scene/poly_cull.h b/src/polyrenderer/scene/poly_cull.h index 53b14c9cc..81fa19387 100644 --- a/src/polyrenderer/scene/poly_cull.h +++ b/src/polyrenderer/scene/poly_cull.h @@ -56,6 +56,8 @@ private: void MarkViewFrustum(); void InvertSegments(); + static bool IsSolidLine(seg_t *line); + bool IsSegmentCulled(angle_t angle1, angle_t angle2) const; void CullNode(void *node); diff --git a/src/polyrenderer/scene/poly_model.cpp b/src/polyrenderer/scene/poly_model.cpp index a49212989..39695c016 100644 --- a/src/polyrenderer/scene/poly_model.cpp +++ b/src/polyrenderer/scene/poly_model.cpp @@ -39,6 +39,7 @@ void PolyRenderModel(PolyRenderThread *thread, const Mat4f &worldToClip, uint32_ renderer.AddLights(actor); renderer.RenderModel(x, y, z, smf, actor, r_viewpoint.TicFrac); PolyTriangleDrawer::SetModelVertexShader(thread->DrawQueue, -1, -1, 0.0f); + PolyTriangleDrawer::SetTransform(thread->DrawQueue, thread->FrameMemory->NewObject(worldToClip), nullptr); } void PolyRenderHUDModel(PolyRenderThread *thread, const Mat4f &worldToClip, uint32_t stencilValue, DPSprite *psp, float ofsx, float ofsy) @@ -143,14 +144,6 @@ IModelVertexBuffer *PolyModelRenderer::CreateVertexBuffer(bool needindex, bool s return new PolyModelVertexBuffer(needindex, singleframe); } -void PolyModelRenderer::SetVertexBuffer(IModelVertexBuffer *buffer) -{ -} - -void PolyModelRenderer::ResetVertexBuffer() -{ -} - VSMatrix PolyModelRenderer::GetViewToWorldMatrix() { Mat4f swapYZ = Mat4f::Null(); diff --git a/src/polyrenderer/scene/poly_model.h b/src/polyrenderer/scene/poly_model.h index bb3008a19..4521951e5 100644 --- a/src/polyrenderer/scene/poly_model.h +++ b/src/polyrenderer/scene/poly_model.h @@ -41,8 +41,6 @@ public: void BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) override; void EndDrawModel(AActor *actor, FSpriteModelFrame *smf) override; IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) override; - void SetVertexBuffer(IModelVertexBuffer *buffer) override; - void ResetVertexBuffer() override; VSMatrix GetViewToWorldMatrix() override; void BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) override; void EndDrawHUDModel(AActor *actor) override; diff --git a/src/posix/cocoa/i_video.mm b/src/posix/cocoa/i_video.mm index 122fbf025..8cc8fe018 100644 --- a/src/posix/cocoa/i_video.mm +++ b/src/posix/cocoa/i_video.mm @@ -204,7 +204,7 @@ class CocoaVideo : public IVideo public: virtual DFrameBuffer* CreateFrameBuffer() override { - auto fb = new OpenGLFrameBuffer(nullptr, fullscreen); + auto fb = new OpenGLRenderer::OpenGLFrameBuffer(nullptr, fullscreen); fb->SetMode(fullscreen, vid_hidpi); fb->SetSize(fb->GetClientWidth(), fb->GetClientHeight()); @@ -527,14 +527,6 @@ void SystemGLFrameBuffer::SetMode(const bool fullscreen, const bool hiDPI) SetWindowedMode(); } - const NSSize viewSize = I_GetContentViewSize(m_window); - - glViewport(0, 0, static_cast(viewSize.width), static_cast(viewSize.height)); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - [[NSOpenGLContext currentContext] flushBuffer]; - [m_window updateTitle]; if (![m_window isKeyWindow]) diff --git a/src/posix/sdl/sdlglvideo.cpp b/src/posix/sdl/sdlglvideo.cpp index b270fcdab..6d8cbd212 100644 --- a/src/posix/sdl/sdlglvideo.cpp +++ b/src/posix/sdl/sdlglvideo.cpp @@ -117,7 +117,7 @@ SDLGLVideo::~SDLGLVideo () DFrameBuffer *SDLGLVideo::CreateFrameBuffer () { - SystemGLFrameBuffer *fb = new OpenGLFrameBuffer(0, fullscreen); + SystemGLFrameBuffer *fb = new OpenGLRenderer::OpenGLFrameBuffer(0, fullscreen); return fb; } diff --git a/src/r_data/gldefs.cpp b/src/r_data/gldefs.cpp index a25eb0c94..0861a9773 100644 --- a/src/r_data/gldefs.cpp +++ b/src/r_data/gldefs.cpp @@ -51,7 +51,7 @@ void AddLightDefaults(FLightDefaults *defaults, double attnFactor); void AddLightAssociation(const char *actor, const char *frame, const char *light); void InitializeActorLights(TArray &LightAssociations); -extern TArray usershaders; +TArray usershaders; extern TDeletingArray LightDefaults; @@ -1154,6 +1154,15 @@ class GLDefsParser tex->bDisableFullbright = disable_fullbright; } + void SetShaderIndex(FTexture *tex, unsigned index) + { + auto desc = usershaders[index - FIRST_USER_SHADER]; + if (desc.disablealphatest) + { + tex->bTranslucent = true; + } + tex->shaderindex = index; + } //========================================================================== // @@ -1370,11 +1379,11 @@ class GLDefsParser usershaders[i].shaderType == usershader.shaderType && !usershaders[i].defines.Compare(usershader.defines)) { - tex->shaderindex = i + FIRST_USER_SHADER; + SetShaderIndex(tex, i + FIRST_USER_SHADER); return; } } - tex->shaderindex = usershaders.Push(usershader) + FIRST_USER_SHADER; + SetShaderIndex(tex, usershaders.Push(usershader) + FIRST_USER_SHADER); } } @@ -1570,6 +1579,10 @@ class GLDefsParser } desc.defines.AppendFormat("#define %s %s\n", defineName.GetChars(), defineValue.GetChars()); } + else if (sc.Compare("disablealphatest")) + { + desc.disablealphatest = true; + } } if (!tex) { @@ -1607,11 +1620,11 @@ class GLDefsParser usershaders[i].shaderType == desc.shaderType && !usershaders[i].defines.Compare(desc.defines)) { - tex->shaderindex = i + FIRST_USER_SHADER; + SetShaderIndex(tex, i + FIRST_USER_SHADER); return; } } - tex->shaderindex = usershaders.Push(desc) + FIRST_USER_SHADER; + SetShaderIndex(tex, usershaders.Push(desc) + FIRST_USER_SHADER); } } } diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index 178aabb6c..e8cb52678 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -48,7 +48,7 @@ #endif CVAR(Bool, gl_interpolate_model_frames, true, CVAR_ARCHIVE) -EXTERN_CVAR(Bool, r_drawvoxels) +EXTERN_CVAR (Bool, r_drawvoxels) extern TDeletingArray Voxels; extern TDeletingArray VoxelDefs; @@ -270,7 +270,6 @@ void FModelRenderer::RenderFrameModels(const FSpriteModelFrame *smf, const FStat FModel * mdl = Models[smf->modelIDs[i]]; FTexture *tex = smf->skinIDs[i].isValid() ? TexMan(smf->skinIDs[i]) : nullptr; mdl->BuildVertexBuffer(this); - SetVertexBuffer(mdl->GetVertexBuffer(this)); mdl->PushSpriteMDLFrame(smf, i); @@ -278,8 +277,6 @@ void FModelRenderer::RenderFrameModels(const FSpriteModelFrame *smf, const FStat mdl->RenderFrame(this, tex, smf->modelframes[i], smfNext->modelframes[i], inter, translation); else mdl->RenderFrame(this, tex, smf->modelframes[i], smf->modelframes[i], 0.f, translation); - - ResetVertexBuffer(); } } } @@ -512,6 +509,7 @@ void InitModels() FVoxelModel *md = (FVoxelModel*)Models[VoxelDefs[i]->Voxel->VoxelIndex]; FSpriteModelFrame smf; memset(&smf, 0, sizeof(smf)); + smf.isVoxel = true; smf.modelIDs[1] = smf.modelIDs[2] = smf.modelIDs[3] = -1; smf.modelIDs[0] = VoxelDefs[i]->Voxel->VoxelIndex; smf.skinIDs[0] = md->GetPaletteTexture(); diff --git a/src/r_data/models/models.h b/src/r_data/models/models.h index e4da6efac..a6b8bf65b 100644 --- a/src/r_data/models/models.h +++ b/src/r_data/models/models.h @@ -70,9 +70,6 @@ public: virtual IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) = 0; - virtual void SetVertexBuffer(IModelVertexBuffer *buffer) = 0; - virtual void ResetVertexBuffer() = 0; - virtual VSMatrix GetViewToWorldMatrix() = 0; virtual void BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) = 0; @@ -478,6 +475,7 @@ struct FSpriteModelFrame float angleoffset; // added pithoffset, rolloffset. float pitchoffset, rolloffset; // I don't want to bother with type transformations, so I made this variables float. + bool isVoxel; }; FSpriteModelFrame * FindModelFrame(const PClass * ti, int sprite, int frame, bool dropped); diff --git a/src/r_data/portalgroups.cpp b/src/r_data/portalgroups.cpp index f1baac476..a22ca157c 100644 --- a/src/r_data/portalgroups.cpp +++ b/src/r_data/portalgroups.cpp @@ -461,9 +461,8 @@ static void GroupLinePortals() void InitPortalGroups() { - if (level.nodes.Size() == 0) return; - - GroupSectorPortals(); + if (level.nodes.Size() > 0) + GroupSectorPortals(); GroupLinePortals(); } diff --git a/src/r_data/renderstyle.cpp b/src/r_data/renderstyle.cpp index 7c27e822c..2ce22d19c 100644 --- a/src/r_data/renderstyle.cpp +++ b/src/r_data/renderstyle.cpp @@ -60,6 +60,8 @@ FRenderStyle LegacyRenderStyles[STYLE_Count] = { { STYLEOP_Add, STYLEALPHA_Src, STYLEALPHA_One, STYLEF_RedIsAlpha | STYLEF_ColorIsFixed } }, /* STYLE_AddShaded */ { { STYLEOP_Add, STYLEALPHA_DstCol, STYLEALPHA_Zero, 0 } }, /* STYLE_Multiply */ { { STYLEOP_Add, STYLEALPHA_InvDstCol, STYLEALPHA_Zero, 0 } }, /* STYLE_InverseMultiply */ + { { STYLEOP_Add, STYLEALPHA_SrcCol, STYLEALPHA_InvSrcCol, 0 } }, /* STYLE_ColorBlend */ + { { STYLEOP_Add, STYLEALPHA_One, STYLEALPHA_Zero, 0 } }, /* STYLE_Source */ }; double GetAlpha(int type, double alpha) diff --git a/src/r_data/renderstyle.h b/src/r_data/renderstyle.h index 04970f962..8bedb4cc5 100644 --- a/src/r_data/renderstyle.h +++ b/src/r_data/renderstyle.h @@ -44,6 +44,19 @@ enum OPAQUE = 65536, }; +enum ETexMode +{ + TM_NORMAL = 0, // (r, g, b, a) + TM_STENCIL, // (1, 1, 1, a) + TM_OPAQUE, // (r, g, b, 1) + TM_INVERSE, // (1-r, 1-g, 1-b, a) + TM_ALPHATEXTURE, // (1, 1, 1, r) + TM_CLAMPY, // (r, g, b, (t >= 0.0 && t <= 1.0)? a:0) + TM_INVERTOPAQUE, // (1-r, 1-g, 1-b, 1) + TM_FOGLAYER, // (renders a fog layer in the shape of the active texture) + TM_FIXEDCOLORMAP = TM_FOGLAYER, // repurposes the objectcolor uniforms to render a fixed colormap range. (Same constant because they cannot be used in the same context. +}; + // Legacy render styles enum ERenderStyle { @@ -63,6 +76,8 @@ enum ERenderStyle STYLE_AddShaded, // Treat patch data as alpha values for alphacolor STYLE_Multiply, // Multiply source with destination (HW renderer only.) STYLE_InverseMultiply, // Multiply source with inverse of destination (HW renderer only.) + STYLE_ColorBlend, // Use color intensity as transparency factor + STYLE_Source, // No blending (only used internally) STYLE_Count }; @@ -141,6 +156,7 @@ union FRenderStyle inline FRenderStyle &operator= (ERenderStyle legacy); bool operator==(const FRenderStyle &o) const { return AsDWORD == o.AsDWORD; } + bool operator!=(const FRenderStyle &o) const { return AsDWORD != o.AsDWORD; } void CheckFuzz(); bool IsVisible(double alpha) const throw(); private: @@ -157,11 +173,6 @@ inline FRenderStyle DefaultRenderStyle() return LegacyRenderStyles[STYLE_Normal]; } -inline FRenderStyle BadRenderStyle() // This is just a marker to find places where work is still needed. -{ - return LegacyRenderStyles[STYLE_Normal]; -} - inline FRenderStyle &FRenderStyle::operator= (ERenderStyle legacy) { if (legacy < STYLE_None || legacy >= STYLE_Count) diff --git a/src/r_defs.h b/src/r_defs.h index ffe526969..f3da27262 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -266,17 +266,21 @@ struct FRemapTable; enum { - SECSPAC_Enter = 1, // Trigger when player enters - SECSPAC_Exit = 2, // Trigger when player exits - SECSPAC_HitFloor = 4, // Trigger when player hits floor - SECSPAC_HitCeiling = 8, // Trigger when player hits ceiling - SECSPAC_Use = 16, // Trigger when player uses - SECSPAC_UseWall = 32, // Trigger when player uses a wall - SECSPAC_EyesDive = 64, // Trigger when player eyes go below fake floor - SECSPAC_EyesSurface = 128, // Trigger when player eyes go above fake floor - SECSPAC_EyesBelowC = 256, // Trigger when player eyes go below fake ceiling - SECSPAC_EyesAboveC = 512, // Trigger when player eyes go above fake ceiling - SECSPAC_HitFakeFloor= 1024, // Trigger when player hits fake floor + SECSPAC_Enter = 1<< 0, // Trigger when player enters + SECSPAC_Exit = 1<< 1, // Trigger when player exits + SECSPAC_HitFloor = 1<< 2, // Trigger when player hits floor + SECSPAC_HitCeiling = 1<< 3, // Trigger when player hits ceiling + SECSPAC_Use = 1<< 4, // Trigger when player uses + SECSPAC_UseWall = 1<< 5, // Trigger when player uses a wall + SECSPAC_EyesDive = 1<< 6, // Trigger when player eyes go below fake floor + SECSPAC_EyesSurface = 1<< 7, // Trigger when player eyes go above fake floor + SECSPAC_EyesBelowC = 1<< 8, // Trigger when player eyes go below fake ceiling + SECSPAC_EyesAboveC = 1<< 9, // Trigger when player eyes go above fake ceiling + SECSPAC_HitFakeFloor= 1<<10, // Trigger when player hits fake floor + SECSPAC_DamageFloor = 1<<11, // Trigger when floor is damaged + SECSPAC_DamageCeiling=1<<12, // Trigger when ceiling is damaged + SECSPAC_DeathFloor = 1<<13, // Trigger when floor has 0 hp + SECSPAC_DeathCeiling= 1<<14, // Trigger when ceiling has 0 hp }; struct secplane_t @@ -588,16 +592,6 @@ struct secspecial_t short damageinterval; // Interval for damage application short leakydamage; // chance of leaking through radiation suit int Flags; - - secspecial_t() - { - Clear(); - } - - void Clear() - { - memset(this, 0, sizeof(*this)); - } }; FSerializer &Serialize(FSerializer &arc, const char *key, secspecial_t &spec, secspecial_t *def); @@ -1092,7 +1086,6 @@ public: int ibocount; // number of indices per plane (identical for all planes.) If this is -1 the index buffer is not in use. float GetReflect(int pos) { return gl_plane_reflection_i? reflect[pos] : 0; } - bool VBOHeightcheck(int pos) const { return vboheight[pos] == GetPlaneTexZ(pos); } FSectorPortalGroup *GetPortalGroup(int plane) { return portals[plane]; } enum @@ -1101,6 +1094,13 @@ public: INVALIDATE_OTHER = 2 }; + // [ZZ] these are for destructible sectors. + // default is 0, which means no special behavior + int healthfloor; + int healthceiling; + int healthfloorgroup; + int healthceilinggroup; + }; struct ReverbContainer; @@ -1318,6 +1318,8 @@ struct line_t unsigned portalindex; unsigned portaltransferred; AutomapLineStyle automapstyle; + int health; // [ZZ] for destructible geometry (0 = no special behavior) + int healthgroup; // [ZZ] this is the "destructible object" id DVector2 Delta() const { diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index d5093fde6..f0ec171c6 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -80,7 +80,7 @@ struct FRandomSoundList struct FPlayerClassLookup { FString Name; - uint16_t ListIndex[3]; // indices into PlayerSounds (0xffff means empty) + uint16_t ListIndex[GENDER_MAX]; // indices into PlayerSounds (0xffff means empty) }; // Used to lookup a sound like "*grunt". This contains all player sounds for @@ -1622,7 +1622,8 @@ static int S_AddPlayerClass (const char *name) FPlayerClassLookup lookup; lookup.Name = name; - lookup.ListIndex[2] = lookup.ListIndex[1] = lookup.ListIndex[0] = 0xffff; + for(int i = 0; i < GENDER_MAX; i++) + lookup.ListIndex[i] = 0xffff; cnum = (int)PlayerClassLookups.Push (lookup); PlayerClassesIsSorted = false; @@ -1770,11 +1771,11 @@ static int S_LookupPlayerSound (int classidx, int gender, FSoundID refid) { int g; - for (g = 0; g < 3 && listidx == 0xffff; ++g) + for (g = 0; g < GENDER_MAX && listidx == 0xffff; ++g) { listidx = PlayerClassLookups[classidx].ListIndex[g]; } - if (g == 3) + if (g == GENDER_MAX) { // No sounds defined at all for this class (can this happen?) if (classidx != DefPlayerClass) { @@ -1796,7 +1797,7 @@ static int S_LookupPlayerSound (int classidx, int gender, FSoundID refid) { // This sound is unavailable. if (ingender != 0) { // Try "male" - return S_LookupPlayerSound (classidx, 0, refid); + return S_LookupPlayerSound (classidx, GENDER_MALE, refid); } if (classidx != DefPlayerClass) { // Try the default class. @@ -1912,7 +1913,7 @@ bool S_AreSoundsEquivalent (AActor *actor, int id1, int id2) int S_FindSkinnedSound (AActor *actor, FSoundID refid) { const char *pclass; - int gender = GENDER_MALE; + int gender = 0; if (actor != NULL && actor->IsKindOf(RUNTIME_CLASS(APlayerPawn))) { @@ -2074,7 +2075,7 @@ void S_MarkPlayerSounds (const char *playerclass) { classidx = DefPlayerClass; } - for (int g = 0; g < 3; ++g) + for (int g = 0; g < GENDER_MAX; ++g) { int listidx = PlayerClassLookups[classidx].ListIndex[0]; if (listidx != 0xffff) @@ -2178,7 +2179,7 @@ CCMD (playersounds) for (i = 0; i < PlayerClassLookups.Size(); ++i) { - for (j = 0; j < 3; ++j) + for (j = 0; j < GENDER_MAX; ++j) { if ((l = PlayerClassLookups[i].ListIndex[j]) != 0xffff) { diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 30e6f918e..be12d3693 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -2737,8 +2737,8 @@ DEFINE_ACTION_FUNCTION(DObject, S_ChangeMusic) PARAM_PROLOGUE; PARAM_STRING(music); PARAM_INT_DEF(order); - PARAM_BOOL(looping); - PARAM_BOOL(force); + PARAM_BOOL_DEF(looping); + PARAM_BOOL_DEF(force); ACTION_RETURN_BOOL(S_ChangeMusic(music, order, looping, force)); } diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 919c6d953..b6059a49c 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -382,7 +382,11 @@ void FxExpression::EmitCompare(VMFunctionBuilder *build, bool invert, TArrayReturnTypes.Size() != Args.Size()) + { + int severity = ctx.Version >= MakeVersion(3, 7) ? MSG_ERROR : MSG_WARNING; + ScriptPosition.Message(severity, "Incorrect number of return values. Got %u, but expected %u", Args.Size(), ctx.ReturnProto->ReturnTypes.Size()); + if (severity == MSG_ERROR) + { + delete this; + return nullptr; + } + // For older script versions this must fall through. + } + if (Args.Size() == 0) { TArray none(0); @@ -10645,7 +10662,7 @@ FxExpression *FxReturnStatement::Resolve(FCompileContext &ctx) } retproto = Args[0]->ReturnProto(); } - else if (ctx.ReturnProto != nullptr && ctx.ReturnProto->ReturnTypes.Size() == Args.Size()) + else { for (unsigned i = 0; i < Args.Size(); i++) { @@ -10660,12 +10677,6 @@ FxExpression *FxReturnStatement::Resolve(FCompileContext &ctx) } return this; // no point calling CheckReturn here. } - else - { - ScriptPosition.Message(MSG_ERROR, "Incorrect number of return values. Got %u, but expected %u", Args.Size(), ctx.ReturnProto->ReturnTypes.Size()); - delete this; - return nullptr; - } ctx.CheckReturn(retproto, ScriptPosition); diff --git a/src/skins.cpp b/src/skins.cpp index c3f5f0a4d..72b988a3a 100644 --- a/src/skins.cpp +++ b/src/skins.cpp @@ -28,7 +28,8 @@ of the following entries: is only useful when playing Doom. Gender (opt) The gender to use for deciding which player sounds to use for sounds not defined in the [Sounds] section. - Can be male, female, or cyborg. The default is male. + Can be male, female, neutral or cyborg. + The default is male. ColorRange (opt) The range of palette entries to recolor to match the player's color. Specified as ,. For Doom, this defaults to 112,127. For Heretic, diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index c638afbe7..b3cebfccf 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -82,6 +82,7 @@ public: virtual bool Preprocess(MIDIStreamer *song, bool looping); virtual FString GetStats(); virtual int GetDeviceType() const { return MDEV_DEFAULT; } + virtual bool CanHandleSysex() const { return true; } }; diff --git a/src/sound/mididevices/music_win_mididevice.cpp b/src/sound/mididevices/music_win_mididevice.cpp index 2e1a4c5a9..f9820033f 100644 --- a/src/sound/mididevices/music_win_mididevice.cpp +++ b/src/sound/mididevices/music_win_mididevice.cpp @@ -86,6 +86,12 @@ public: bool Update() override; void PrecacheInstruments(const uint16_t *instruments, int count); DWORD PlayerLoop(); + bool CanHandleSysex() const override + { + // No Sysex for GS synth. + return VolumeWorks; + } + //protected: static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD); diff --git a/src/sound/midisources/midisource.h b/src/sound/midisources/midisource.h index 7e7381eae..12e0188b4 100644 --- a/src/sound/midisources/midisource.h +++ b/src/sound/midisources/midisource.h @@ -25,6 +25,7 @@ class MIDISource protected: bool isLooping = false; + bool skipSysex = false; int Division = 0; int Tempo = 500000; int InitialTempo = 500000; @@ -54,6 +55,8 @@ public: LoopLimit = looplimit; isLooping = looped; } + + void SkipSysex() { skipSysex = true; } bool isValid() const { return Division > 0; } int getDivision() const { return Division; } diff --git a/src/sound/midisources/midisource_hmi.cpp b/src/sound/midisources/midisource_hmi.cpp index 0c555268c..1fe08e9cc 100644 --- a/src/sound/midisources/midisource_hmi.cpp +++ b/src/sound/midisources/midisource_hmi.cpp @@ -644,7 +644,7 @@ uint32_t *HMISong::SendCommand (uint32_t *events, TrackInfo *track, uint32_t del if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = ReadVarLen(track); - if (len >= (MAX_MIDI_EVENTS-1)*3*4) + if (len >= (MAX_MIDI_EVENTS-1)*3*4 || skipSysex) { // This message will never fit. Throw it away. track->TrackP += len; } diff --git a/src/sound/midisources/midisource_smf.cpp b/src/sound/midisources/midisource_smf.cpp index d7e8808f1..42299b7f8 100644 --- a/src/sound/midisources/midisource_smf.cpp +++ b/src/sound/midisources/midisource_smf.cpp @@ -585,7 +585,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = track->ReadVarLen(); - if (len >= (MAX_MIDI_EVENTS-1)*3*4) + if (len >= (MAX_MIDI_EVENTS-1)*3*4 || skipSysex) { // This message will never fit. Throw it away. track->TrackP += len; } diff --git a/src/sound/midisources/midisource_xmi.cpp b/src/sound/midisources/midisource_xmi.cpp index caad0897c..3e2fcac87 100644 --- a/src/sound/midisources/midisource_xmi.cpp +++ b/src/sound/midisources/midisource_xmi.cpp @@ -515,7 +515,7 @@ uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t dela if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = track->ReadVarLen(); - if (len >= (MAX_MIDI_EVENTS-1)*3*4) + if (len >= (MAX_MIDI_EVENTS-1)*3*4 || skipSysex) { // This message will never fit. Throw it away. track->EventP += len; } diff --git a/src/sound/musicformats/music_midistream.cpp b/src/sound/musicformats/music_midistream.cpp index 85c4135de..60061b264 100644 --- a/src/sound/musicformats/music_midistream.cpp +++ b/src/sound/musicformats/music_midistream.cpp @@ -364,6 +364,7 @@ bool MIDIStreamer::InitPlayback() } source->CheckCaps(MIDI->GetTechnology()); + if (!MIDI->CanHandleSysex()) source->SkipSysex(); if (MIDI->Preprocess(this, m_Looping)) { diff --git a/src/swrenderer/line/r_renderdrawsegment.cpp b/src/swrenderer/line/r_renderdrawsegment.cpp index f21c9e79f..fbc18b0c6 100644 --- a/src/swrenderer/line/r_renderdrawsegment.cpp +++ b/src/swrenderer/line/r_renderdrawsegment.cpp @@ -159,6 +159,9 @@ namespace swrenderer auto viewport = Thread->Viewport.get(); Clip3DFloors *clip3d = Thread->Clip3D.get(); + if (curline->sidedef->GetTexture(side_t::mid).isNull()) + return false; + FTexture *tex = TexMan(curline->sidedef->GetTexture(side_t::mid), true); if (i_compatflags & COMPATF_MASKEDMIDTEX) { diff --git a/src/swrenderer/r_swscene.cpp b/src/swrenderer/r_swscene.cpp index 26b1e4060..f68336e85 100644 --- a/src/swrenderer/r_swscene.cpp +++ b/src/swrenderer/r_swscene.cpp @@ -61,22 +61,6 @@ public: } }; -class FSWSceneTexture : public FTexture -{ -public: - - FSWSceneTexture(int w, int h, int bits) - { - Width = w; - Height = h; - WidthBits = bits; - UseType = ETextureType::SWCanvas; - bNoCompress = true; - SystemTexture[0] = screen->CreateHardwareTexture(this); - } - - // This is just a wrapper around the hardware texture and should never call the bitmap getters - if it does, something is wrong. -}; //========================================================================== // @@ -106,7 +90,7 @@ sector_t *SWSceneDrawer::RenderView(player_t *player) { // This manually constructs its own material here. fbtex.reset(); - fbtex.reset(new FSWSceneTexture(screen->GetWidth(), screen->GetHeight(), V_IsTrueColor())); + fbtex.reset(new FWrapperTexture(screen->GetWidth(), screen->GetHeight(), V_IsTrueColor())); fbtex->SystemTexture[0]->AllocateBuffer(screen->GetWidth(), screen->GetHeight(), V_IsTrueColor() ? 4 : 1); auto mat = FMaterial::ValidateTexture(fbtex.get(), false); mat->AddTextureLayer(PaletteTexture.get()); diff --git a/src/swrenderer/r_swscene.h b/src/swrenderer/r_swscene.h index b57e3b947..9ed0bec03 100644 --- a/src/swrenderer/r_swscene.h +++ b/src/swrenderer/r_swscene.h @@ -8,12 +8,12 @@ #include "c_cvars.h" #include -class FSWSceneTexture; +class FWrapperTexture; class SWSceneDrawer { std::unique_ptr PaletteTexture; - std::unique_ptr FBTexture[2]; + std::unique_ptr FBTexture[2]; int FBTextureIndex = 0; bool FBIsTruecolor = false; std::unique_ptr Canvas; diff --git a/src/swrenderer/things/r_model.cpp b/src/swrenderer/things/r_model.cpp index 70995c32c..1f02e0b6c 100644 --- a/src/swrenderer/things/r_model.cpp +++ b/src/swrenderer/things/r_model.cpp @@ -218,14 +218,6 @@ namespace swrenderer return new SWModelVertexBuffer(needindex, singleframe); } - void SWModelRenderer::SetVertexBuffer(IModelVertexBuffer *buffer) - { - } - - void SWModelRenderer::ResetVertexBuffer() - { - } - VSMatrix SWModelRenderer::GetViewToWorldMatrix() { // Calculate the WorldToView matrix as it would have looked like without yshearing: diff --git a/src/swrenderer/things/r_model.h b/src/swrenderer/things/r_model.h index bd9b868dc..dc746e8c5 100644 --- a/src/swrenderer/things/r_model.h +++ b/src/swrenderer/things/r_model.h @@ -65,8 +65,6 @@ namespace swrenderer void BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) override; void EndDrawModel(AActor *actor, FSpriteModelFrame *smf) override; IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) override; - void SetVertexBuffer(IModelVertexBuffer *buffer) override; - void ResetVertexBuffer() override; VSMatrix GetViewToWorldMatrix() override; void BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) override; void EndDrawHUDModel(AActor *actor) override; diff --git a/src/textures/formats/canvastexture.cpp b/src/textures/formats/canvastexture.cpp index 2cb8b807f..ae4a4697e 100644 --- a/src/textures/formats/canvastexture.cpp +++ b/src/textures/formats/canvastexture.cpp @@ -186,4 +186,3 @@ bool FCanvasTexture::CheckModified (FRenderStyle) } return false; } - diff --git a/src/textures/formats/multipatchtexture.cpp b/src/textures/formats/multipatchtexture.cpp index 3027048d6..fb9381808 100644 --- a/src/textures/formats/multipatchtexture.cpp +++ b/src/textures/formats/multipatchtexture.cpp @@ -234,7 +234,7 @@ FMultiPatchTexture::FMultiPatchTexture (const void *texdef, FPatchLookup *patchl int i; mtexture.d = (const maptexture_t *)texdef; - bMultiPatch = true; + bMultiPatch = 1; if (strife) { @@ -713,7 +713,7 @@ FTexture *FMultiPatchTexture::GetRedirect() FTexture *FMultiPatchTexture::GetRawTexture() { - return NumParts == 1 ? Parts->Texture : this; + return NumParts == 1 && UseType == ETextureType::Wall && bMultiPatch == 1 && Scale == Parts->Texture->Scale ? Parts->Texture : this; } //========================================================================== @@ -1071,7 +1071,7 @@ FMultiPatchTexture::FMultiPatchTexture (FScanner &sc, ETextureType usetype) TArray inits; bool bSilent = false; - bMultiPatch = true; + bMultiPatch = 2; sc.SetCMode(true); sc.MustGetString(); const char* textureName = NULL; diff --git a/src/textures/texture.cpp b/src/textures/texture.cpp index 88ae5b176..047a9b205 100644 --- a/src/textures/texture.cpp +++ b/src/textures/texture.cpp @@ -1489,6 +1489,23 @@ void FDummyTexture::SetSize (int width, int height) CalcBitSize (); } + +//========================================================================== +// +// +// +//========================================================================== + +FWrapperTexture::FWrapperTexture(int w, int h, int bits) +{ + Width = w; + Height = h; + WidthBits = bits; + UseType = ETextureType::SWCanvas; + bNoCompress = true; + SystemTexture[0] = screen->CreateHardwareTexture(this); +} + //========================================================================== // // Debug stuff diff --git a/src/textures/textures.h b/src/textures/textures.h index 89ce9d85b..d064441d1 100644 --- a/src/textures/textures.h +++ b/src/textures/textures.h @@ -77,8 +77,12 @@ struct UserShaderDesc FString shader; MaterialShaderIndex shaderType; FString defines; + bool disablealphatest = false; }; +extern TArray usershaders; + + struct FloatRect { float left,top; @@ -259,7 +263,7 @@ public: uint8_t bComplex:1; // Will be used to mark extended MultipatchTextures that have to be // fully composited before subjected to any kind of postprocessing instead of // doing it per patch. - uint8_t bMultiPatch:1; // This is a multipatch texture (we really could use real type info for textures...) + uint8_t bMultiPatch:2; // This is a multipatch texture (we really could use real type info for textures...) uint8_t bKeepAround:1; // This texture was used as part of a multi-patch texture. Do not free it. uint8_t bFullNameTexture : 1; uint8_t bBrightmapChecked : 1; // Set to 1 if brightmap has been checked @@ -789,6 +793,13 @@ public: friend struct FCanvasTextureInfo; }; +// A wrapper around a hardware texture, to allow using it in the 2D drawing interface. +class FWrapperTexture : public FTexture +{ +public: + FWrapperTexture(int w, int h, int bits = 1); +}; + extern FTextureManager TexMan; #endif diff --git a/src/v_2ddrawer.cpp b/src/v_2ddrawer.cpp index a2388b65b..40cea9904 100644 --- a/src/v_2ddrawer.cpp +++ b/src/v_2ddrawer.cpp @@ -203,22 +203,22 @@ bool F2DDrawer::SetStyle(FTexture *tex, DrawParms &parms, PalEntry &vertexcolor, if (style.Flags & STYLEF_RedIsAlpha) { - quad.mDrawMode = DTM_AlphaTexture; + quad.mDrawMode = TM_ALPHATEXTURE; } else { - quad.mDrawMode = DTM_Stencil; + quad.mDrawMode = TM_STENCIL; } } else { if (style.Flags & STYLEF_RedIsAlpha) { - quad.mDrawMode = DTM_AlphaTexture; + quad.mDrawMode = TM_ALPHATEXTURE; } else if (style.Flags & STYLEF_InvertSource) { - quad.mDrawMode = DTM_Invert; + quad.mDrawMode = TM_INVERSE; } if (parms.specialcolormap != nullptr) @@ -237,9 +237,9 @@ bool F2DDrawer::SetStyle(FTexture *tex, DrawParms &parms, PalEntry &vertexcolor, if (!parms.masked) { - // For DTM_AlphaTexture and DTM_Stencil the mask cannot be turned off because it would not yield a usable result. - if (quad.mDrawMode == DTM_Normal) quad.mDrawMode = DTM_Opaque; - else if (quad.mDrawMode == DTM_Invert) quad.mDrawMode = DTM_InvertOpaque; + // For TM_ALPHATEXTURE and TM_STENCIL the mask cannot be turned off because it would not yield a usable result. + if (quad.mDrawMode == TM_NORMAL) quad.mDrawMode = TM_OPAQUE; + else if (quad.mDrawMode == TM_INVERSE) quad.mDrawMode = TM_INVERTOPAQUE; } quad.mRenderStyle = parms.style; // this contains the blend mode and blend equation settings. if (parms.burn) quad.mFlags |= DTF_Burn; @@ -574,10 +574,10 @@ void F2DDrawer::AddColorOnlyQuad(int x1, int y1, int w, int h, PalEntry color, F // //========================================================================== -void F2DDrawer::AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color) +void F2DDrawer::AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color, uint8_t alpha) { PalEntry p = color ? (PalEntry)color : GPalette.BaseColors[palcolor]; - p.a = 255; + p.a = alpha; RenderCommand dg; @@ -596,9 +596,10 @@ void F2DDrawer::AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t c // //========================================================================== -void F2DDrawer::AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color) +void F2DDrawer::AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color, uint8_t alpha) { PalEntry p = (PalEntry)color; + p.a = alpha; DVector2 point0(x1, y1); DVector2 point1(x2, y2); diff --git a/src/v_2ddrawer.h b/src/v_2ddrawer.h index 5cef4f719..9f7f16676 100644 --- a/src/v_2ddrawer.h +++ b/src/v_2ddrawer.h @@ -39,16 +39,6 @@ public: DrawTypePoints, }; - enum ETextureDrawMode : uint8_t - { - DTM_Normal = 0, - DTM_Stencil = 1, - DTM_Opaque = 2, - DTM_Invert = 3, - DTM_AlphaTexture = 4, - DTM_InvertOpaque = 6, - }; - enum ETextureFlags : uint8_t { DTF_Wrap = 1, @@ -101,7 +91,7 @@ public: int mDesaturate; FRenderStyle mRenderStyle; PalEntry mColor1; // Overlay color - ETextureDrawMode mDrawMode; + ETexMode mDrawMode; uint8_t mFlags; RenderCommand() @@ -149,8 +139,8 @@ public: void AddClear(int left, int top, int right, int bottom, int palcolor, uint32_t color); - void AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color); - void AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color); + void AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color, uint8_t alpha = 255); + void AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color, uint8_t alpha = 255); void AddPixel(int x1, int y1, int palcolor, uint32_t color); void Clear(); diff --git a/src/v_draw.cpp b/src/v_draw.cpp index e3babb060..f2b5a7800 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -1075,9 +1075,9 @@ void DFrameBuffer::FillBorder (FTexture *img) // //========================================================================== -void DFrameBuffer::DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32_t realcolor) +void DFrameBuffer::DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32_t realcolor, uint8_t alpha) { - m2DDrawer.AddLine(x0, y0, x1, y1, palColor, realcolor); + m2DDrawer.AddLine(x0, y0, x1, y1, palColor, realcolor, alpha); } DEFINE_ACTION_FUNCTION(_Screen, DrawLine) @@ -1088,13 +1088,14 @@ DEFINE_ACTION_FUNCTION(_Screen, DrawLine) PARAM_INT(x1); PARAM_INT(y1); PARAM_INT(color); + PARAM_INT_DEF(alpha); if (!screen->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function"); - screen->DrawLine(x0, y0, x1, y1, -1, color); + screen->DrawLine(x0, y0, x1, y1, -1, color, alpha); return 0; } -void DFrameBuffer::DrawThickLine(int x0, int y0, int x1, int y1, double thickness, uint32_t realcolor) { - m2DDrawer.AddThickLine(x0, y0, x1, y1, thickness, realcolor); +void DFrameBuffer::DrawThickLine(int x0, int y0, int x1, int y1, double thickness, uint32_t realcolor, uint8_t alpha) { + m2DDrawer.AddThickLine(x0, y0, x1, y1, thickness, realcolor, alpha); } DEFINE_ACTION_FUNCTION(_Screen, DrawThickLine) @@ -1106,8 +1107,9 @@ DEFINE_ACTION_FUNCTION(_Screen, DrawThickLine) PARAM_INT(y1); PARAM_FLOAT(thickness); PARAM_INT(color); + PARAM_INT_DEF(alpha); if (!screen->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function"); - screen->DrawThickLine(x0, y0, x1, y1, thickness, color); + screen->DrawThickLine(x0, y0, x1, y1, thickness, color, alpha); return 0; } diff --git a/src/v_font.cpp b/src/v_font.cpp index 0a7d74215..4678c56b9 100644 --- a/src/v_font.cpp +++ b/src/v_font.cpp @@ -87,6 +87,7 @@ The FON2 header is followed by variable length data: #include "cmdlib.h" #include "sc_man.h" #include "hu_stuff.h" +#include "gstrings.h" #include "v_text.h" #include "vm.h" @@ -919,7 +920,9 @@ DEFINE_ACTION_FUNCTION(FFont, StringWidth) { PARAM_SELF_STRUCT_PROLOGUE(FFont); PARAM_STRING(str); - ACTION_RETURN_INT(self->StringWidth(str)); + const char *txt = str[0] == '$' ? GStrings(&str[1]) : str.GetChars(); + + ACTION_RETURN_INT(self->StringWidth(txt)); } //========================================================================== diff --git a/src/v_framebuffer.cpp b/src/v_framebuffer.cpp index b4a5807f9..c89d3cf8a 100644 --- a/src/v_framebuffer.cpp +++ b/src/v_framebuffer.cpp @@ -48,6 +48,9 @@ #include "vm.h" #include "r_videoscale.h" #include "i_time.h" +#include "hwrenderer/scene/hw_portal.h" +#include "hwrenderer/utility/hw_clock.h" +#include "hwrenderer/data/flatvertices.h" CVAR(Bool, gl_scale_viewport, true, CVAR_ARCHIVE); @@ -172,6 +175,12 @@ DSimpleCanvas::~DSimpleCanvas () DFrameBuffer::DFrameBuffer (int width, int height) { SetSize(width, height); + mPortalState = new FPortalSceneState; +} + +DFrameBuffer::~DFrameBuffer() +{ + delete mPortalState; } void DFrameBuffer::SetSize(int width, int height) @@ -279,6 +288,24 @@ void DFrameBuffer::GetFlashedPalette(PalEntry pal[256]) DoBlending(SourcePalette, pal, 256, Flash.r, Flash.g, Flash.b, Flash.a); } +void DFrameBuffer::Update() +{ + CheckBench(); + + int initialWidth = GetClientWidth(); + int initialHeight = GetClientHeight(); + int clientWidth = ViewportScaledWidth(initialWidth, initialHeight); + int clientHeight = ViewportScaledHeight(initialWidth, initialHeight); + if (clientWidth < 320) clientWidth = 320; + if (clientHeight < 200) clientHeight = 200; + if (clientWidth > 0 && clientHeight > 0 && (GetWidth() != clientWidth || GetHeight() != clientHeight)) + { + SetVirtualSize(clientWidth, clientHeight); + V_OutputResized(clientWidth, clientHeight); + mVertexData->OutputResized(clientWidth, clientHeight); + } +} + PalEntry *DFrameBuffer::GetPalette() { return SourcePalette; @@ -297,6 +324,14 @@ void DFrameBuffer::GetFlash(PalEntry &rgb, int &amount) amount = Flash.a; } +void DFrameBuffer::SetClearColor(int color) +{ + PalEntry pe = GPalette.BaseColors[color]; + mSceneClearColor[0] = pe.r / 255.f; + mSceneClearColor[1] = pe.g / 255.f; + mSceneClearColor[2] = pe.b / 255.f; + mSceneClearColor[3] = 1.f; +} //========================================================================== // @@ -526,30 +561,3 @@ void DFrameBuffer::ScaleCoordsFromWindow(int16_t &x, int16_t &y) x = int16_t((x - letterboxX) * Width / letterboxWidth); y = int16_t((y - letterboxY) * Height / letterboxHeight); } - -//=========================================================================== -// -// -// -//=========================================================================== - -#define DBGBREAK assert(0) - -class DDummyFrameBuffer : public DFrameBuffer -{ - typedef DFrameBuffer Super; -public: - DDummyFrameBuffer(int width, int height) - : DFrameBuffer(0, 0) - { - SetVirtualSize(width, height); - } - // These methods should never be called. - void Update() { DBGBREAK; } - bool IsFullscreen() { DBGBREAK; return 0; } - int GetClientWidth() { DBGBREAK; return 0; } - int GetClientHeight() { DBGBREAK; return 0; } - - float Gamma; -}; - diff --git a/src/v_text.cpp b/src/v_text.cpp index 30d8972fb..7e4a44b89 100644 --- a/src/v_text.cpp +++ b/src/v_text.cpp @@ -45,6 +45,7 @@ #include "gstrings.h" #include "vm.h" +#include "serializer.h" int ListGetInt(VMVa_List &tags); @@ -345,7 +346,7 @@ static void breakit (FBrokenLines *line, FFont *font, const uint8_t *start, cons line->Width = font->StringWidth (line->Text); } -FBrokenLines *V_BreakLines (FFont *font, int maxwidth, const uint8_t *string, bool preservecolor, unsigned int *count) +TArray V_BreakLines (FFont *font, int maxwidth, const uint8_t *string, bool preservecolor) { TArray Lines(128); @@ -448,71 +449,62 @@ FBrokenLines *V_BreakLines (FFont *font, int maxwidth, const uint8_t *string, bo } } } - - // Make a copy of the broken lines and return them - FBrokenLines *broken = new FBrokenLines[Lines.Size() + 1]; - - for (unsigned ii = 0; ii < Lines.Size(); ++ii) - { - broken[ii] = Lines[ii]; - } - broken[Lines.Size()].Width = -1; - if (count != nullptr) - { - *count = Lines.Size(); - } - - return broken; + return Lines; } -void V_FreeBrokenLines (FBrokenLines *lines) +FSerializer &Serialize(FSerializer &arc, const char *key, FBrokenLines& g, FBrokenLines *def) { - if (lines) + if (arc.BeginObject(key)) { - delete[] lines; + arc("text", g.Text) + ("width", g.Width) + .EndObject(); } + return arc; } + + class DBrokenLines : public DObject { - DECLARE_ABSTRACT_CLASS(DBrokenLines, DObject) + DECLARE_CLASS(DBrokenLines, DObject) public: - FBrokenLines *mBroken; - unsigned int mCount; + TArray mBroken; - DBrokenLines(FBrokenLines *broken, unsigned int count) + DBrokenLines() = default; + + DBrokenLines(TArray &broken) { - mBroken = broken; - mCount = count; + mBroken = std::move(broken); } - void OnDestroy() override + void Serialize(FSerializer &arc) override { - V_FreeBrokenLines(mBroken); + arc("lines", mBroken); } }; -IMPLEMENT_CLASS(DBrokenLines, true, false); +IMPLEMENT_CLASS(DBrokenLines, false, false); DEFINE_ACTION_FUNCTION(DBrokenLines, Count) { PARAM_SELF_PROLOGUE(DBrokenLines); - ACTION_RETURN_INT(self->mCount); + ACTION_RETURN_INT(self->mBroken.Size()); } DEFINE_ACTION_FUNCTION(DBrokenLines, StringWidth) { PARAM_SELF_PROLOGUE(DBrokenLines); PARAM_INT(index); - ACTION_RETURN_INT((unsigned)index >= self->mCount? -1 : self->mBroken[index].Width); + ACTION_RETURN_INT((unsigned)index >= self->mBroken.Size()? -1 : self->mBroken[index].Width); } DEFINE_ACTION_FUNCTION(DBrokenLines, StringAt) { PARAM_SELF_PROLOGUE(DBrokenLines); PARAM_INT(index); - ACTION_RETURN_STRING((unsigned)index >= self->mCount? -1 : self->mBroken[index].Text); + ACTION_RETURN_STRING((unsigned)index >= self->mBroken.Size() ? -1 : self->mBroken[index].Text); } DEFINE_ACTION_FUNCTION(FFont, BreakLines) @@ -521,7 +513,6 @@ DEFINE_ACTION_FUNCTION(FFont, BreakLines) PARAM_STRING(text); PARAM_INT(maxwidth); - unsigned int count; - FBrokenLines *broken = V_BreakLines(self, maxwidth, text, true, &count); - ACTION_RETURN_OBJECT(Create(broken, count)); + auto broken = V_BreakLines(self, maxwidth, text, true); + ACTION_RETURN_OBJECT(Create(broken)); } diff --git a/src/v_text.h b/src/v_text.h index cd7d3e6ad..30efa286f 100644 --- a/src/v_text.h +++ b/src/v_text.h @@ -39,7 +39,7 @@ struct FBrokenLines { - int Width; + unsigned Width; FString Text; }; @@ -79,12 +79,11 @@ struct FBrokenLines #define TEXTCOLOR_CHAT "\034*" #define TEXTCOLOR_TEAMCHAT "\034!" -FBrokenLines *V_BreakLines (FFont *font, int maxwidth, const uint8_t *str, bool preservecolor = false, unsigned int *count = nullptr); -void V_FreeBrokenLines (FBrokenLines *lines); -inline FBrokenLines *V_BreakLines (FFont *font, int maxwidth, const char *str, bool preservecolor = false, unsigned int *count = nullptr) - { return V_BreakLines (font, maxwidth, (const uint8_t *)str, preservecolor, count); } -inline FBrokenLines *V_BreakLines (FFont *font, int maxwidth, const FString &str, bool preservecolor = false, unsigned int *count = nullptr) - { return V_BreakLines (font, maxwidth, (const uint8_t *)str.GetChars(), preservecolor, count); } +TArray V_BreakLines (FFont *font, int maxwidth, const uint8_t *str, bool preservecolor = false); +inline TArray V_BreakLines (FFont *font, int maxwidth, const char *str, bool preservecolor = false) + { return V_BreakLines (font, maxwidth, (const uint8_t *)str, preservecolor); } +inline TArray V_BreakLines (FFont *font, int maxwidth, const FString &str, bool preservecolor = false) + { return V_BreakLines (font, maxwidth, (const uint8_t *)str.GetChars(), preservecolor); } int GetCharFromString(const uint8_t *&string); diff --git a/src/v_video.cpp b/src/v_video.cpp index 76b8770dc..c94f141d2 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -103,6 +103,7 @@ CUSTOM_CVAR(Int, vid_rendermode, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOIN // No further checks needed. All this changes now is which scene drawer the render backend calls. } +CVAR(Int, vid_renderer, 1, 0) // for some stupid mods which threw caution out of the window... EXTERN_CVAR(Bool, r_blendmethod) @@ -127,6 +128,7 @@ public: bool IsFullscreen() { DBGBREAK; return 0; } int GetClientWidth() { DBGBREAK; return 0; } int GetClientHeight() { DBGBREAK; return 0; } + void InitializeState() override {} float Gamma; }; @@ -593,6 +595,7 @@ bool IVideo::SetResolution () } screen = buff; + screen->InitializeState(); screen->SetGamma(); V_UpdateModeSize(screen->GetWidth(), screen->GetHeight()); diff --git a/src/v_video.h b/src/v_video.h index 72ae6e0a5..b2fe73657 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -34,6 +34,7 @@ #ifndef __V_VIDEO_H__ #define __V_VIDEO_H__ +#include #include "doomtype.h" #include "vectors.h" @@ -43,11 +44,20 @@ #include "c_cvars.h" #include "v_colortables.h" #include "v_2ddrawer.h" -#include +#include "hwrenderer/dynlights/hw_shadowmap.h" struct sector_t; class IShaderProgram; class FTexture; +struct FPortalSceneState; +class FSkyVertexBuffer; +class IIndexBuffer; +class IVertexBuffer; +class IDataBuffer; +class FFlatVertexBuffer; +class GLViewpointBuffer; +class FLightBuffer; +struct HWDrawInfo; enum EHWCaps { @@ -64,6 +74,7 @@ enum EHWCaps RFL_DEBUG = 128, }; + struct IntRect { int left, top; @@ -330,7 +341,6 @@ public: class FUniquePalette; class IHardwareTexture; class FTexture; -class IUniformBuffer; // A canvas that represents the actual display. The video code is responsible // for actually implementing this. Built on top of SimpleCanvas, because it @@ -360,18 +370,32 @@ protected: PalEntry SourcePalette[256]; // This is where unpaletted textures get their palette from public: - int hwcaps = 0; - float glslversion = 0; // This is here so that the differences between old OpenGL and new OpenGL/Vulkan can be handled by platform independent code. - int instack[2] = { 0,0 }; // this is globally maintained state for portal recursion avoidance. - bool enable_quadbuffered = false; + // Hardware render state that needs to be exposed to the API independent part of the renderer. For ease of access this is stored in the base class. + int hwcaps = 0; // Capability flags + float glslversion = 0; // This is here so that the differences between old OpenGL and new OpenGL/Vulkan can be handled by platform independent code. + int instack[2] = { 0,0 }; // this is globally maintained state for portal recursion avoidance. + int stencilValue = 0; // Global stencil test value + bool enable_quadbuffered = false; // Quad-buffered stereo available? + unsigned int uniformblockalignment = 256; // Hardware dependent uniform buffer alignment. + unsigned int maxuniformblock = 65536; + const char *gl_vendorstring; // On OpenGL (not Vulkan) we have to account for some issues with Intel. + FPortalSceneState *mPortalState; // global portal state. + FSkyVertexBuffer *mSkyData = nullptr; // the sky vertex buffer + FFlatVertexBuffer *mVertexData = nullptr; // Global vertex data + GLViewpointBuffer *mViewpoints = nullptr; // Viewpoint render data. + FLightBuffer *mLights = nullptr; // Dynamic lights + IShadowMap mShadowMap; IntRect mScreenViewport; IntRect mSceneViewport; IntRect mOutputLetterbox; + float mSceneClearColor[4]; + public: DFrameBuffer (int width=1, int height=1); - virtual ~DFrameBuffer() {} + virtual ~DFrameBuffer(); + virtual void InitializeState() = 0; // For stuff that needs 'screen' set. void SetSize(int width, int height); void SetVirtualSize(int width, int height) @@ -393,7 +417,7 @@ public: } // Make the surface visible. - virtual void Update () = 0; + virtual void Update (); // Return a pointer to 256 palette entries that can be written to. PalEntry *GetPalette (); @@ -441,8 +465,11 @@ public: virtual void BlurScene(float amount) {} // Interface to hardware rendering resources - virtual IUniformBuffer *CreateUniformBuffer(size_t size, bool staticuse = false) { return nullptr; } virtual IShaderProgram *CreateShaderProgram() { return nullptr; } + virtual IVertexBuffer *CreateVertexBuffer() { return nullptr; } + virtual IIndexBuffer *CreateIndexBuffer() { return nullptr; } + virtual IDataBuffer *CreateDataBuffer(int bindingpoint, bool ssbo) { return nullptr; } + bool BuffersArePersistent() { return !!(hwcaps & RFL_BUFFER_STORAGE); } // Begin/End 2D drawing operations. void Begin2D() { isIn2D = true; } @@ -467,8 +494,7 @@ public: // Report a game restart void InitPalette(); - virtual void InitForLevel() {} - virtual void SetClearColor(int color) {} + void SetClearColor(int color); virtual uint32_t GetCaps(); virtual void RenderTextureView(FCanvasTexture *tex, AActor *Viewpoint, double FOV); virtual void WriteSavePic(player_t *player, FileWriter *file, int width, int height); @@ -509,10 +535,10 @@ public: void Clear(int left, int top, int right, int bottom, int palcolor, uint32_t color); // Draws a line - void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32_t realcolor); + void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32_t realcolor, uint8_t alpha = 255); // Draws a line with thickness - void DrawThickLine(int x0, int y0, int x1, int y1, double thickness, uint32_t realcolor); + void DrawThickLine(int x0, int y0, int x1, int y1, double thickness, uint32_t realcolor, uint8_t alpha = 255); // Draws a single pixel void DrawPixel(int x, int y, int palcolor, uint32_t rgbcolor); diff --git a/src/win32/gl_sysfb.cpp b/src/win32/gl_sysfb.cpp index f966454e3..bc704bd69 100644 --- a/src/win32/gl_sysfb.cpp +++ b/src/win32/gl_sysfb.cpp @@ -52,9 +52,6 @@ #include "doomerrors.h" #include "win32glvideo.h" -#include "gl/renderer/gl_renderer.h" -#include "gl/system/gl_framebuffer.h" - extern HWND Window; PFNWGLSWAPINTERVALEXTPROC myWglSwapIntervalExtProc; diff --git a/src/win32/win32glvideo.cpp b/src/win32/win32glvideo.cpp index c3650a807..2fb24b464 100644 --- a/src/win32/win32glvideo.cpp +++ b/src/win32/win32glvideo.cpp @@ -90,7 +90,7 @@ DFrameBuffer *Win32GLVideo::CreateFrameBuffer() { SystemGLFrameBuffer *fb; - fb = new OpenGLFrameBuffer(m_hMonitor, fullscreen); + fb = new OpenGLRenderer::OpenGLFrameBuffer(m_hMonitor, fullscreen); return fb; } diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 6a58cb0cf..c5fe1da0a 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2317,7 +2317,8 @@ OPTVAL_ON = "On"; OPTVAL_AUTO = "Auto"; OPTVAL_MALE = "Male"; OPTVAL_FEMALE = "Female"; -OPTVAL_OTHER = "Other"; +OPTVAL_NEUTRAL = "Neutral"; +OPTVAL_OTHER = "Object"; OPTVAL_UPPERLEFT = "Upper left"; OPTVAL_UPPERRIGHT = "Upper right"; OPTVAL_LOWERLEFT = "Lower left"; diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index d3764371e..e83fc78f4 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -88,6 +88,10 @@ DoomEdNums 9503 = "$SetCeilingSlope" 9510 = "$CopyFloorPlane" 9511 = "$CopyCeilingPlane" + 9600 = SecActDamageFloor + 9601 = SecActDamageCeiling + 9602 = SecActDeathFloor + 9603 = SecActDeathCeiling 9800 = PointLight 9801 = PointLightPulse 9802 = PointLightFlicker diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 640b6186c..4bb577ac6 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -368,7 +368,8 @@ OptionValue "Gender" { 0, "$OPTVAL_MALE" 1, "$OPTVAL_FEMALE" - 2, "$OPTVAL_OTHER" + 2, "$OPTVAL_NEUTRAL" + 3, "$OPTVAL_OTHER" } ListMenu "PlayerMenu" diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index f096d0dd7..dd082e614 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -76,7 +76,7 @@ vec4 getTexel(vec2 st) // switch (uTextureMode) { - case 1: // TM_MASK + case 1: // TM_STENCIL texel.rgb = vec3(1.0,1.0,1.0); break; @@ -88,7 +88,7 @@ vec4 getTexel(vec2 st) texel = vec4(1.0-texel.r, 1.0-texel.b, 1.0-texel.g, texel.a); break; - case 4: // TM_REDTOALPHA + case 4: // TM_ALPHATEXTURE { float gray = grayscale(texel); texel = vec4(1.0, 1.0, 1.0, gray*texel.a); diff --git a/wadsrc/static/shaders/glsl/main.vp b/wadsrc/static/shaders/glsl/main.vp index e25438c02..0be91afb8 100644 --- a/wadsrc/static/shaders/glsl/main.vp +++ b/wadsrc/static/shaders/glsl/main.vp @@ -20,21 +20,8 @@ void main() vec2 parmTexCoord; vec4 parmPosition; - #ifndef USE_QUAD_DRAWER - parmTexCoord = aTexCoord; - parmPosition = aPosition; - #else - if (uQuadMode == 0) - { - parmTexCoord = aTexCoord; - parmPosition = aPosition; - } - else - { - parmPosition = uQuadVertices[int(aPosition.x)]; - parmTexCoord = uQuadTexCoords[int(aPosition.x)].st; - } - #endif + parmTexCoord = aTexCoord; + parmPosition = aPosition; #ifndef SIMPLE vec4 worldcoord = ModelMatrix * mix(parmPosition, aVertex2, uInterpolationFactor); diff --git a/wadsrc/static/shaders/glsl/material_pbr.fp b/wadsrc/static/shaders/glsl/material_pbr.fp index 6ef60b66f..6859c28bb 100644 --- a/wadsrc/static/shaders/glsl/material_pbr.fp +++ b/wadsrc/static/shaders/glsl/material_pbr.fp @@ -56,6 +56,11 @@ float quadraticDistanceAttenuation(vec4 lightpos) return attenuation; } +float linearDistanceAttenuation(vec4 lightpos) +{ + float lightdistance = distance(lightpos.xyz, pixelpos.xyz); + return clamp((lightpos.w - lightdistance) / lightpos.w, 0.0, 1.0); +} vec3 ProcessMaterialLight(Material material, vec3 ambientLight) { @@ -93,7 +98,7 @@ vec3 ProcessMaterialLight(Material material, vec3 ambientLight) vec3 L = normalize(lightpos.xyz - worldpos); vec3 H = normalize(V + L); - float attenuation = quadraticDistanceAttenuation(lightpos); + float attenuation = linearDistanceAttenuation(lightpos); if (lightspot1.w == 1.0) attenuation *= spotLightAttenuation(lightpos, lightspot1.xyz, lightspot2.x, lightspot2.y); if (lightcolor.a < 0.0) @@ -133,7 +138,7 @@ vec3 ProcessMaterialLight(Material material, vec3 ambientLight) vec3 L = normalize(lightpos.xyz - worldpos); vec3 H = normalize(V + L); - float attenuation = quadraticDistanceAttenuation(lightpos); + float attenuation = linearDistanceAttenuation(lightpos); if (lightspot1.w == 1.0) attenuation *= spotLightAttenuation(lightpos, lightspot1.xyz, lightspot2.x, lightspot2.y); if (lightcolor.a < 0.0) diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 082b69532..449bfe054 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -1,4 +1,4 @@ -version "3.4" +version "3.7" #include "zscript/base.txt" #include "zscript/sounddata.txt" #include "zscript/mapdata.txt" diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 2ce2e7663..767b87e0f 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -183,6 +183,8 @@ class Actor : Thinker native native int DesignatedTeam; native Actor BlockingMobj; native Line BlockingLine; + native Sector BlockingCeiling; + native Sector BlockingFloor; native int PoisonDamage; native name PoisonDamageType; native int PoisonDuration; @@ -683,6 +685,7 @@ class Actor : Thinker native native clearscope vector2 Vec2Angle(double length, double angle, bool absolute = false) const; native clearscope vector2 Vec2Offset(double x, double y, bool absolute = false) const; native clearscope vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false) const; + native void VelIntercept(Actor targ, double speed = -1, bool aimpitch = true, bool oldvel = false); native void VelFromAngle(double speed = 0, double angle = 0); native void Vel3DFromAngle(double speed, double angle, double pitch); native void Thrust(double speed = 0, double angle = 0); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index e648febd1..314b59b3a 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -1,5 +1,6 @@ struct _ native // These are the global variables, the struct is only here to avoid extending the parser for this. { + native readonly Array AllClasses; native readonly Array > AllActorClasses; native readonly Array<@PlayerClass> PlayerClasses; native readonly Array<@PlayerSkin> PlayerSkins; @@ -193,8 +194,8 @@ struct Screen native native static vararg void DrawShape(TextureID tex, bool animate, Shape2D s, ...); native static vararg void DrawChar(Font font, int normalcolor, double x, double y, int character, ...); native static vararg void DrawText(Font font, int normalcolor, double x, double y, String text, ...); - native static void DrawLine(int x0, int y0, int x1, int y1, Color color); - native static void DrawThickLine(int x0, int y0, int x1, int y1, double thickness, Color color); + native static void DrawLine(int x0, int y0, int x1, int y1, Color color, int alpha = 255); + native static void DrawThickLine(int x0, int y0, int x1, int y1, double thickness, Color color, int alpha = 255); native static void DrawFrame(int x, int y, int w, int h); native static Vector2, Vector2 VirtualToRealCoords(Vector2 pos, Vector2 size, Vector2 vsize, bool vbottom=false, bool handleaspect=true); native static double GetAspectRatio(); diff --git a/wadsrc/static/zscript/compatibility.txt b/wadsrc/static/zscript/compatibility.txt index eaf77e0a4..43280bdcc 100644 --- a/wadsrc/static/zscript/compatibility.txt +++ b/wadsrc/static/zscript/compatibility.txt @@ -11,7 +11,7 @@ extend class Object { let f = Font.GetFont(fontname); if (f == null) return; - return Console.MidPrint(f, textlabel, bold); + Console.MidPrint(f, textlabel, bold); } } diff --git a/wadsrc/static/zscript/level_compatibility.txt b/wadsrc/static/zscript/level_compatibility.txt index e007e98dd..e7bd242be 100644 --- a/wadsrc/static/zscript/level_compatibility.txt +++ b/wadsrc/static/zscript/level_compatibility.txt @@ -5,6 +5,22 @@ class LevelCompatibility play { switch (checksum) { + case '9527DD0809FDA39CCFC316A21D135783': // HACX.WAD map05 + { + // fix non-functional self-referencing sector hack. + for(int i = 578; i < 582; i++) + SetLineSectorRef(i, Line.back, 91); + + for(int i = 707; i < 714; i++) + SetLineSectorRef(i, Line.back, 91); + + SetLineSectorRef(736, Line.front, 91); + SetLineSectorRef(659, Line.front, 91); + SetLineSpecial(659, Transfer_Heights, 60); + break; + } + + case 'E2B5D1400279335811C1C1C0B437D9C8': // Deathknights of the Dark Citadel, map54 { // This map has two gear boxes which are flagged for player cross diff --git a/wadsrc/static/zscript/mapdata.txt b/wadsrc/static/zscript/mapdata.txt index f20a9b2eb..9dc42da22 100644 --- a/wadsrc/static/zscript/mapdata.txt +++ b/wadsrc/static/zscript/mapdata.txt @@ -156,6 +156,9 @@ struct Line native play native int locknumber; // [Dusk] lock number for special native readonly uint portalindex; native readonly uint portaltransferred; + + native readonly int health; + native readonly int healthgroup; native bool isLinePortal(); native bool isVisualPortal(); @@ -286,6 +289,11 @@ struct Sector native play native int16 leakydamage; native readonly uint16 ZoneNumber; + + native readonly int healthceiling; + native readonly int healthfloor; + native readonly int healthceilinggroup; + native readonly int healthfloorgroup; enum ESectorMoreFlags { diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index e7c05e7d6..dce27e106 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -1366,6 +1366,14 @@ enum EPlayerState PST_GONE // Player has left the game } +enum EPlayerGender +{ + GENDER_MALE, + GENDER_FEMALE, + GENDER_NEUTRAL, + GENDER_OTHER +} + struct PlayerInfo native play // this is what internally is known as player_t { // technically engine constants but the only part of the playsim using them is the player. diff --git a/wadsrc/static/zscript/shared/sectoraction.txt b/wadsrc/static/zscript/shared/sectoraction.txt index fb7edfb19..6b93d9805 100644 --- a/wadsrc/static/zscript/shared/sectoraction.txt +++ b/wadsrc/static/zscript/shared/sectoraction.txt @@ -4,17 +4,21 @@ class SectorAction : Actor // self class uses health to define the activation type. enum EActivation { - SECSPAC_Enter = 1, - SECSPAC_Exit = 2, - SECSPAC_HitFloor = 4, - SECSPAC_HitCeiling = 8, - SECSPAC_Use = 16, - SECSPAC_UseWall = 32, - SECSPAC_EyesDive = 64, - SECSPAC_EyesSurface = 128, - SECSPAC_EyesBelowC = 256, - SECSPAC_EyesAboveC = 512, - SECSPAC_HitFakeFloor= 1024, + SECSPAC_Enter = 1<< 0, + SECSPAC_Exit = 1<< 1, + SECSPAC_HitFloor = 1<< 2, + SECSPAC_HitCeiling = 1<< 3, + SECSPAC_Use = 1<< 4, + SECSPAC_UseWall = 1<< 5, + SECSPAC_EyesDive = 1<< 6, + SECSPAC_EyesSurface = 1<< 7, + SECSPAC_EyesBelowC = 1<< 8, + SECSPAC_EyesAboveC = 1<< 9, + SECSPAC_HitFakeFloor = 1<<10, + SECSPAC_DamageFloor = 1<<11, + SECSPAC_DamageCeiling = 1<<12, + SECSPAC_DeathFloor = 1<<13, + SECSPAC_DeathCeiling = 1<<14 }; default @@ -200,6 +204,54 @@ class SecActHitFakeFloor : SectorAction } } +// Triggered when sector's floor is damaged ---------------------------------- +class SecActDamageFloor : SectorAction +{ + Default + { + Health SECSPAC_DamageFloor; + } + + // [ZZ] damage is unconditional, so this as well + override bool CanTrigger (Actor triggerer) { return !!special; } +} + +// Triggered when sector's ceiling is damaged ---------------------------------- +class SecActDamageCeiling : SectorAction +{ + Default + { + Health SECSPAC_DamageCeiling; + } + + // [ZZ] damage is unconditional, so this as well + override bool CanTrigger (Actor triggerer) { return !!special; } +} + +// Triggered when sector's floor is reduced to 0 hp ---------------------------------- +class SecActDeathFloor : SectorAction +{ + Default + { + Health SECSPAC_DeathFloor; + } + + // [ZZ] damage is unconditional, so this as well + override bool CanTrigger (Actor triggerer) { return !!special; } +} + +// Triggered when sector's ceiling is reduced to 0 hp ---------------------------------- +class SecActDeathCeiling : SectorAction +{ + Default + { + Health SECSPAC_DeathCeiling; + } + + // [ZZ] damage is unconditional, so this as well + override bool CanTrigger (Actor triggerer) { return !!special; } +} + //========================================================================== // // Music changer. Uses the sector action class to do its job diff --git a/wadsrc/static/zscript/statusbar/statusbar.txt b/wadsrc/static/zscript/statusbar/statusbar.txt index 6b74e3d01..385db36bf 100644 --- a/wadsrc/static/zscript/statusbar/statusbar.txt +++ b/wadsrc/static/zscript/statusbar/statusbar.txt @@ -318,6 +318,8 @@ class BaseStatusBar native ui native void BeginStatusBar(bool forceScaled = false, int resW = -1, int resH = -1, int rel = -1); native void BeginHUD(double Alpha = 1., bool forcescaled = false, int resW = -1, int resH = -1); + native void UpdateScreenGeometry(); + virtual void Init() { mSmallFont = HUDFont.Create("SmallFont"); @@ -330,7 +332,7 @@ class BaseStatusBar native ui native virtual clearscope void SetMugShotState (String state_name, bool wait_till_done=false, bool reset=false); virtual void FlashItem (class itemtype) { artiflashTick = 4; itemflashFade = 0.75; } - virtual void AttachToPlayer (PlayerInfo player) { CPlayer = player; } + virtual void AttachToPlayer (PlayerInfo player) { CPlayer = player; UpdateScreenGeometry(); } virtual void FlashCrosshair () { CrosshairSize = XHAIRPICKUPSIZE; } virtual void NewGame () { if (CPlayer != null) AttachToPlayer(CPlayer); } virtual void ShowPop (int popnum) { ShowLog = (popnum == POP_Log && !ShowLog); }