Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 26905) +++ ps/trunk/binaries/data/config/default.cfg (revision 26906) @@ -1,589 +1,589 @@ ; Global Configuration Settings ; ; ************************************************************** ; * DO NOT EDIT THIS FILE if you want personal customisations: * ; * create a text file called "local.cfg" instead, and copy * ; * the lines from this file that you want to change. * ; * * ; * If a setting is part of a section (for instance [hotkey]) * ; * you need to append the section name at the beginning of * ; * your custom line (for instance you need to write * ; * "hotkey.pause = Space" if you want to change the pausing * ; * hotkey to the spacebar). * ; * * ; * On Linux, create: * ; * $XDG_CONFIG_HOME/0ad/config/local.cfg * ; * (Note: $XDG_CONFIG_HOME defaults to ~/.config) * ; * * ; * On OS X, create: * ; * ~/Library/Application\ Support/0ad/config/local.cfg * ; * * ; * On Windows, create: * ; * %appdata%\0ad\config\local.cfg * ; * * ; ************************************************************** ; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.) windowed = false ; Switches between real fullscreen and borderless window on a full display size. borderless.fullscreen = true ; Hides a window border in the windowed mode. borderless.window = false ; Show detailed tooltips (Unit stats) showdetailedtooltips = false ; Pause the game on window focus loss (Only applicable to single player mode) pauseonfocusloss = true ; Persist settings after leaving the game setup screen persistmatchsettings = true ; Default player name to use in multiplayer ; playername = "anonymous" ; Default server name or IP to use in multiplayer multiplayerserver = "127.0.0.1" ; Force a particular resolution. (If these are 0, the default is ; to keep the current desktop resolution in fullscreen mode or to ; use 1024x768 in windowed mode.) xres = 0 yres = 0 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth) bpp = 0 ; Preferred display (for multidisplay setups, only works with SDL 2.0) display = 0 ; Enable Hi-DPI where supported, currently working only for testing. hidpi = false ; Allows to force GL version for SDL forceglversion = false forceglprofile = "compatibility" ; Possible values: compatibility, core, es forceglmajorversion = 3 forceglminorversion = 3 ; Big screenshot tiles screenshot.tiles = 8 screenshot.tilewidth = 480 screenshot.tileheight = 270 ; Emulate right-click with Ctrl+Click on Mac mice macmouse = false ; System settings: ; if false, actors won't be rendered but anything entity will be. renderactors = true watereffects=true ; When disabled, force usage of the fixed pipeline water. This is faster, but really, really ugly. waterfancyeffects = false waterrealdepth = true waterrefraction = true waterreflection = true shadows = true shadowquality = 0 ; Shadow map resolution. (-1 - Low, 0 - Medium, 1 - High, 2 - Very High) ; High values can crash the game when using a graphics card with low memory! shadowpcf = true ; Increases details closer to the camera but decreases performance ; especially on low hardware. shadowscascadecount = 1 shadowscascadedistanceratio = 1.7 ; Hides shadows after the distance. shadowscutoffdistance = 300.0 ; If true shadows cover the whole map instead of the camera frustum. shadowscovermap = false vsync = false particles = true fog = true silhouettes = true showsky = true ; Uses a synchonized call to a GL driver to get an error state. Useful ; for a debugging of a system without GL_KHR_debug. gl.checkerrorafterswap = false ; Different ways to draw a cursor, possible values are "sdl" and "system". ; The "system" one doesn't support a visual change of the cursor. cursorbackend = "sdl" ; Backends for all graphics rendering: ; glarb - GL with legacy assembler-like shaders, might used only for buggy drivers. ; gl - GL with GLSL shaders, should be used by default. ; dummy - backend that does nothing, allows to check performance without backend drivers. rendererbackend = "gl" ; Enables additional debug information in renderer backend. renderer.backend.debugcontext = "false" renderer.backend.debugmessages = "false" renderer.backend.debuglabels = "false" renderer.backend.debugscopedlabels = "false" ; Should not be edited. It's used only for preventing of running fixed pipeline. renderpath = default ; (0 - low, 1 - medium, 2 - high), higher quality means worse performance. textures.quality = 2 ; (1, 2, 4, 8 and 16) textures.maxanisotropy = 2 ;;;;; EXPERIMENTAL ;;;;; ; Experimental probably-non-working GPU skinning support; requires GLSL; use at own risk gpuskinning = false ; Use smooth LOS interpolation -smoothlos = false +smoothlos = true ; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath. postproc = false ; Use anti-aliasing techniques. antialiasing = "disabled" ; Use sharpening techniques. sharpening = "disabled" sharpness = 0.3 ; Quality used for actors. max_actor_quality=200 ; Whether or not actor variants are selected randomly, possible values are "full", "limited", "none". variant_diversity = "full" ; Quality level of shader effects (set to 10 to display all effects) materialmgr.quality = 2.0 ;;;;;;;;;;;;;;;;;;;;;;;; ; Color of the sky (in "r g b" format) skycolor = "0 0 0" [adaptivefps] session = 60 ; Throttle FPS in running games (prevents 100% CPU workload). menu = 60 ; Throttle FPS in menus only. [profiler2] server = "127.0.0.1" server.port = "8000" ; Use a free port on your machine. server.threads = "6" ; Enough for the browser's parallel connection limit [hotkey] ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+'. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS exit = "" ; 'Custom' exit to desktop, SDL handles the native command via SDL_Quit. cancel = Escape ; Close or cancel the current dialog box/popup confirm = Return ; Confirm the current command pause = Pause, "Shift+Space" ; Pause/unpause game screenshot = F2 ; Take PNG screenshot bigscreenshot = "Shift+F2" ; Take large BMP screenshot togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots wireframe = "Alt+Shift+W" ; Toggle wireframe mode silhouettes = "Alt+Shift+S" ; Toggle unit silhouettes ; > DIALOG HOTKEYS summary = "Ctrl+Tab" ; Toggle in-game summary lobby = "Alt+L" ; Show the multiplayer lobby in a dialog window. structree = "Alt+Shift+T" ; Show structure tree civinfo = "Alt+Shift+H" ; Show civilization info ; > CLIPBOARD CONTROLS copy = "Ctrl+C" ; Copy to clipboard paste = "Ctrl+V" ; Paste from clipboard cut = "Ctrl+X" ; Cut selected text and copy to the clipboard ; > CONSOLE SETTINGS console.toggle = BackQuote, F9 ; Open/close console ; > OVERLAY KEYS fps.toggle = "Alt+F" ; Toggle frame counter realtime.toggle = "Alt+T" ; Toggle current display of computer time timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter ceasefirecounter.toggle = "" ; Toggle ceasefire counter ; > HOTKEYS ONLY chat = Return ; Toggle chat window teamchat = "T" ; Toggle chat window in team chat mode privatechat = "L" ; Toggle chat window and select the previous private chat partner ; > QUICKSAVE quicksave = "Shift+F5" quickload = "Shift+F8" [hotkey.camera] reset = "R" ; Reset camera rotation to default. follow = "F" ; Follow the first unit in the selection rallypointfocus = "" ; Focus the camera on the rally point of the selected building lastattackfocus = "Space" ; Focus the camera on the last notified attack zoom.in = Plus, NumPlus ; Zoom camera in (continuous control) zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) pan = MouseMiddle ; Enable scrolling by moving mouse left = A, LeftArrow ; Scroll or rotate left right = D, RightArrow ; Scroll or rotate right up = W, UpArrow ; Scroll or rotate up/forwards down = S, DownArrow ; Scroll or rotate down/backwards scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed [hotkey.camera.jump] 1 = F5 ; Jump to position N 2 = F6 3 = F7 4 = F8 ;5 = ;6 = ;7 = ;8 = ;9 = ;10 = [hotkey.camera.jump.set] 1 = "Ctrl+F5" ; Set jump position N 2 = "Ctrl+F6" 3 = "Ctrl+F7" 4 = "Ctrl+F8" ;5 = ;6 = ;7 = ;8 = ;9 = ;10 = [hotkey.profile] toggle = "F11" ; Enable/disable real-time profiler save = "Shift+F11" ; Save current profiler data to logs/profile.txt [hotkey.profile2] toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler [hotkey.selection] cancel = Esc ; Un-select all units and cancel building placement add = Shift ; Add units to selection militaryonly = Alt ; Add only military units to the selection nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection idleonly = "I" ; Select only idle units woundedonly = "O" ; Select only wounded units remove = Ctrl ; Remove units from selection idlebuilder = Semicolon ; Select next idle builder idleworker = Period, NumDecimal ; Select next idle worker idlewarrior = Slash, NumDivide ; Select next idle warrior idleunit = BackSlash ; Select next idle unit offscreen = Alt ; Include offscreen units in selection singleselection = "" ; Modifier to select units individually, opposed to per formation. [hotkey.selection.group.add] 1 = "Shift+1", "Shift+Num1" 2 = "Shift+2", "Shift+Num2" 3 = "Shift+3", "Shift+Num3" 4 = "Shift+4", "Shift+Num4" 5 = "Shift+5", "Shift+Num5" 6 = "Shift+6", "Shift+Num6" 7 = "Shift+7", "Shift+Num7" 8 = "Shift+8", "Shift+Num8" 9 = "Shift+9", "Shift+Num9" 10 = "Shift+0", "Shift+Num0" [hotkey.selection.group.save] 1 = "Ctrl+1", "Ctrl+Num1" 2 = "Ctrl+2", "Ctrl+Num2" 3 = "Ctrl+3", "Ctrl+Num3" 4 = "Ctrl+4", "Ctrl+Num4" 5 = "Ctrl+5", "Ctrl+Num5" 6 = "Ctrl+6", "Ctrl+Num6" 7 = "Ctrl+7", "Ctrl+Num7" 8 = "Ctrl+8", "Ctrl+Num8" 9 = "Ctrl+9", "Ctrl+Num9" 10 = "Ctrl+0", "Ctrl+Num0" [hotkey.selection.group.select] 1 = 1, Num1 2 = 2, Num2 3 = 3, Num3 4 = 4, Num4 5 = 5, Num5 6 = 6, Num6 7 = 7, Num7 8 = 8, Num8 9 = 9, Num9 10 = 0, Num0 [hotkey.gamesetup] mapbrowser.open = "M" [hotkey.session] kill = Delete, Backspace ; Destroy selected units stop = "H" ; Stop the current action backtowork = "Y" ; The unit will go back to work unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected unloadturrets = "U" ; Unload turreted units. leaveturret = "U" ; Leave turret point. move = "" ; Modifier to move to a point instead of another action (e.g. gather) attack = Ctrl ; Modifier to attack instead of another action (e.g. capture) attackmove = Ctrl ; Modifier to attackmove when clicking on a point attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point garrison = Ctrl ; Modifier to garrison when clicking on building occupyturret = Ctrl ; Modifier to occupy a turret when clicking on a turret holder. autorallypoint = Ctrl ; Modifier to set the rally point on the building itself guard = "G" ; Modifier to escort/guard when clicking on unit/building patrol = "P" ; Modifier to patrol a unit repair = "J" ; Modifier to repair when clicking on building/mechanical unit queue = Shift ; Modifier to queue unit orders instead of replacing pushorderfront = "" ; Modifier to push unit orders to the front instead of replacing. orderone = Alt ; Modifier to order only one entity in selection. batchtrain = Shift ; Modifier to train units in batches massbarter = Shift ; Modifier to barter bunch of resources masstribute = Shift ; Modifier to tribute bunch of resources noconfirmation = Shift ; Do not ask confirmation when deleting a building/unit fulltradeswap = Shift ; Modifier to put the desired trade resource to 100% unloadtype = Shift ; Modifier to unload all units of type deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting rotate.cw = RightBracket ; Rotate building placement preview clockwise rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise snaptoedges = Ctrl ; Modifier to align new structures with nearby existing structure toggledefaultformation = "" ; Switch between null default formation and the last default formation used (defaults to "box") flare = K ; Modifier to send a flare to your allies flareactivate = "" ; Modifier to activate the mode to send a flare to your allies calltoarms = "" ; Modifier to call the selected units to the arms. ; Overlays showstatusbars = Tab ; Toggle display of status bars devcommands.toggle = "Alt+D" ; Toggle developer commands panel highlightguarding = PageDown ; Toggle highlight of guarding units highlightguarded = PageUp ; Toggle highlight of guarded units diplomacycolors = "Alt+X" ; Toggle diplomacy colors toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures togglehealrange = "Alt+B" ; Toggle display of heal range overlays of selected units [hotkey.session.gui] toggle = "Alt+G" ; Toggle visibility of session GUI menu.toggle = "F10" ; Toggle in-game menu diplomacy.toggle = "Ctrl+H" ; Toggle in-game diplomacy page barter.toggle = "Ctrl+B" ; Toggle in-game barter/trade page objectives.toggle = "Ctrl+O" ; Toggle in-game objectives page tutorial.toggle = "Ctrl+P" ; Toggle in-game tutorial panel [hotkey.session.savedgames] delete = Delete, Backspace ; Delete the selected saved game asking confirmation noconfirmation = Shift ; Do not ask confirmation when deleting a game [hotkey.session.queueunit] ; > UNIT TRAINING 1 = "Z" ; add first unit type to queue 2 = "X" ; add second unit type to queue 3 = "C" ; add third unit type to queue 4 = "V" ; add fourth unit type to queue 5 = "B" ; add fivth unit type to queue 6 = "N" ; add sixth unit type to queue 7 = "M" ; add seventh unit type to queue 8 = Comma ; add eighth unit type to queue [hotkey.session.timewarp] fastforward = "Ctrl+Space" ; If timewarp mode enabled, speed up the game rewind = "Shift+Backspace" ; If timewarp mode enabled, go back to earlier point in the game [hotkey.tab] next = "Tab", "Alt+S" ; Show the next tab prev = "Shift+Tab", "Alt+W" ; Show the previous tab [hotkey.text] ; > GUI TEXTBOX HOTKEYS delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor delete.right = "Ctrl+Del" ; Delete word to the right of cursor move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor [gui] cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking) scale = 1.0 ; GUI scaling factor, for improved compatibility with 4K displays [gui.gamesetup] enabletips = true ; Enable/Disable tips during gamesetup (for newcomers) assignplayers = everyone ; Whether to assign joining clients to free playerslots. Possible values: everyone, buddies, disabled. aidifficulty = 3 ; Difficulty level, from 0 (easiest) to 5 (hardest) aibehavior = "random" ; Default behavior of the AI (random, balanced, aggressive or defensive) settingsslide = true ; Enable/Disable settings panel slide [gui.loadingscreen] progressdescription = false ; Whether to display the progress percent or a textual description [gui.session] dragdelta = 4 ; Number of pixels the mouse can move before the action is considered a drag camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one? timeelapsedcounter = false ; Show the game duration in the top right corner ceasefirecounter = false ; Show the remaining ceasefire time in the top right corner batchtrainingsize = 5 ; Number of units to be trained per batch by default (when pressing the hotkey) scrollbatchratio = 1 ; Number of times you have to scroll to increase/decrease the batchsize by 1 flarelifetime = 6 ; How long the flare markers on the minimap are displayed in seconds woundedunithotkeythreshold = 33 ; The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number attackrange = true ; Display attack range overlays of selected defensive structures aurasrange = true ; Display aura range overlays of selected units and structures healrange = true ; Display heal range overlays of selected units rankabovestatusbar = true ; Show rank icons above status bars experiencestatusbar = true ; Show an experience status bar above each selected unit respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending) snaptoedges = "disabled" ; Possible values: disabled, enabled. snaptoedgesdistancethreshold = 15 ; On which distance we don't snap to edges disjointcontrolgroups = "true" ; Whether control groups are disjoint sets or entities can be in multiple control groups at the same time. defaultformation = "special/formations/box" ; For walking orders, automatically put units into this formation if they don't have one already. formationwalkonly = "true" ; Formations are disabled when giving gather/attack/... orders. howtoshownames = 0 ; Whether the specific names are show as default, as opposed to the generic names. And whether the secondary names are shown. (0 - show both; specific names primary, 1 - show both; generic names primary, 2 - show only specific names, 3 - show only generic names) selectformationasone = "true" ; Whether to select formations as a whole by default. [gui.session.minimap] ; Icons that are displayed for some entities on a minimap. icons.enabled = "true" icons.opacity = 1.0 icons.sizescale = 1.0 blinkduration = 1.7 ; The blink duration while pinging pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification [gui.session.notifications] attack = true ; Show a chat notification if you are attacked by another player tribute = true ; Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode barter = true ; Show a chat notification to observers when a player bartered resources phase = completed ; Show a chat notification if you or an ally have started, aborted or completed a new phase, and phases of all players in observer mode. Possible values: none, completed, all. [gui.splashscreen] enable = true ; Enable/disable the splashscreen version = 0 ; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch [gui.session.diplomacycolors] self = "21 55 149" ; Color of your units when diplomacy colors are enabled ally = "86 180 31" ; Color of allies when diplomacy colors are enabled neutral = "231 200 5" ; Color of neutral players when diplomacy colors are enabled enemy = "150 20 20" ; Color of enemies when diplomacy colors are enabled [joystick] ; EXPERIMENTAL: joystick/gamepad settings enable = false deadzone = 8192 [chat] timestamp = true ; Show at which time chat messages have been sent [chat.session] extended = true ; Whether to display the chat history [lobby] history = 0 ; Number of past messages to display on join room = "arena26" ; Default MUC room to join server = "lobby.wildfiregames.com" ; Address of lobby server tls = true ; Whether to use TLS encryption when connecting to the server. verify_certificate = false ; Whether to reject connecting to the lobby if the TLS certificate is invalid (TODO: wait for Gloox GnuTLS trust implementation to be fixed) terms_url = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/prelobby/common/terms/"; Allows the user to save the text and print the terms terms_of_service = "0" ; Version (hash) of the Terms of Service that the user has accepted terms_of_use = "0" ; Version (hash) of the Terms of Use that the user has accepted privacy_policy = "0" ; Version (hash) of the Privacy Policy that the user has accepted xpartamupp = "wfgbot26" ; Name of the server-side XMPP-account that manage games echelon = "echelon26" ; Name of the server-side XMPP-account that manages ratings buddies = "," ; Comma separated list of playernames that the current user has marked as buddies rememberpassword = true ; Whether to store the encrypted password in the user config [lobby.columns] gamerating = false ; Show the average rating of the participating players in a column of the gamelist [lobby.stun] enabled = true ; The STUN protocol allows hosting games without configuring the firewall and router. ; If STUN is disabled, the game relies on direct connection, UPnP and port forwarding. server = "lobby.wildfiregames.com" ; Address of the STUN server. port = 3478 ; Port of the STUN server. delay = 200 ; Duration in milliseconds that is waited between STUN messages. ; Smaller numbers speed up joins but also become less stable. [mod] enabledmods = "mod public" [modio] public_key = "RWR/X/zDEJSHEfioDC/EO2So3TRMUmAH4O6A1a3ZhMwcqQA61xqBPPGa" ; Public key corresponding to the private key valid mods are signed with disclaimer = "0" ; Version (hash) of the Disclaimer that the user has accepted [modio.v1] baseurl = "https://api.mod.io/v1" api_key = "23df258a71711ea6e4b50893acc1ba55" name_id = "0ad" [network] duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled. observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached observermaxlag = 10 ; Make clients wait for observers if they lag more than X turns behind. -1 means "never wait for observers". autocatchup = true ; Auto-accelerate the sim rate if lagging behind (as an observer). [overlay] fps = "false" ; Show frames per second in top right corner realtime = "false" ; Show current system time in top right corner netwarnings = "true" ; Show warnings if the network connection is bad [profiler2] autoenable = false ; Enable HTTP server output at startup (default off for security/performance) gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available. [rlinterface] address = "127.0.0.1:6000" [sound] mastergain = 0.9 musicgain = 0.2 ambientgain = 0.6 actiongain = 0.7 uigain = 0.7 mindistance = 1 maxdistance = 350 maxstereoangle = 0.62 ; About PI/5 radians [sound.notify] nick = true ; Play a sound when someone mentions your name in the lobby or game gamesetup.join = false ; Play a sound when a new client joins the game setup [tinygettext] debug = false ; Print error messages each time a translation for an English string is not found. [userreport] ; Opt-in online user reporting system url_upload = "https://feedback.wildfiregames.com/report/upload/v1/" ; URL where UserReports are uploaded to url_publication = "https://feedback.wildfiregames.com/" ; URL where UserReports were analyzed and published url_terms = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/userreport/Terms_and_Conditions.txt"; Allows the user to save the text and print the terms terms = "0" ; Version (hash) of the UserReporter Terms that the user has accepted [view] ; Camera control settings scroll.speed = 120.0 scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed scroll.mouse.detectdistance = 3 rotate.x.speed = 1.2 rotate.x.min = 28.0 rotate.x.max = 60.0 rotate.x.default = 35.0 rotate.y.speed = 2.0 rotate.y.speed.wheel = 0.45 rotate.y.default = 0.0 rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed drag.speed = 0.5 zoom.speed = 256.0 zoom.speed.wheel = 32.0 zoom.min = 50.0 zoom.max = 200.0 zoom.default = 120.0 zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed pos.smoothness = 0.1 zoom.smoothness = 0.4 rotate.x.smoothness = 0.5 rotate.y.smoothness = 0.3 near = 2.0 ; Near plane distance far = 4096.0 ; Far plane distance fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide height.smoothness = 0.5 height.min = 16 Index: ps/trunk/binaries/data/mods/public/shaders/arb/los_interp.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/los_interp.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/los_interp.fp (revision 26906) @@ -1,17 +1,17 @@ !!ARBfp1.0 ATTRIB v_los = fragment.texcoord[0]; PARAM delta = program.local[0]; TEMP los1_tex; TEMP los2_tex; TEX los1_tex, v_los, texture[0], 2D; TEX los2_tex, v_los, texture[1], 2D; TEMP smoothing; MOV_SAT smoothing, delta.x; -LRP result.color.a, smoothing, los1_tex.a, los2_tex.a; +LRP result.color, smoothing, los1_tex.rrrr, los2_tex.rrrr; END Index: ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp (revision 26906) @@ -1,1495 +1,1500 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ShaderProgram.h" #include "graphics/Color.h" #include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.h" #define USE_SHADER_XML_VALIDATION 1 #if USE_SHADER_XML_VALIDATION #include "ps/XML/RelaxNG.h" #include "ps/XML/XMLWriter.h" #endif #include #include #include namespace Renderer { namespace Backend { namespace GL { namespace { struct Binding { Binding(int a, int b) : first(a), second(b) { } Binding() : first(-1), second(-1) { } /** * Returns whether this uniform attribute is active in the shader. * If not then there's no point calling Uniform() to set its value. */ bool Active() const { return first != -1 || second != -1; } int first; int second; }; int GetStreamMask(const VertexAttributeStream stream) { return 1 << static_cast(stream); } GLint GLSizeFromFormat(const Format format) { GLint size = 1; if (format == Renderer::Backend::Format::R32_SFLOAT || format == Renderer::Backend::Format::R16_SINT) size = 1; else if ( format == Renderer::Backend::Format::R8G8_UNORM || format == Renderer::Backend::Format::R8G8_UINT || format == Renderer::Backend::Format::R16G16_SINT || format == Renderer::Backend::Format::R32G32_SFLOAT) size = 2; else if (format == Renderer::Backend::Format::R32G32B32_SFLOAT) size = 3; else if ( format == Renderer::Backend::Format::R32G32B32A32_SFLOAT || format == Renderer::Backend::Format::R8G8B8A8_UNORM || format == Renderer::Backend::Format::R8G8B8A8_UINT) size = 4; else debug_warn("Unsupported format."); return size; } GLenum GLTypeFromFormat(const Format format) { GLenum type = GL_FLOAT; if (format == Renderer::Backend::Format::R32_SFLOAT || format == Renderer::Backend::Format::R32G32_SFLOAT || format == Renderer::Backend::Format::R32G32B32_SFLOAT || format == Renderer::Backend::Format::R32G32B32A32_SFLOAT) type = GL_FLOAT; else if ( format == Renderer::Backend::Format::R16_SINT || format == Renderer::Backend::Format::R16G16_SINT) type = GL_SHORT; else if ( format == Renderer::Backend::Format::R8G8_UNORM || format == Renderer::Backend::Format::R8G8_UINT || format == Renderer::Backend::Format::R8G8B8A8_UNORM || format == Renderer::Backend::Format::R8G8B8A8_UINT) type = GL_UNSIGNED_BYTE; else debug_warn("Unsupported format."); return type; } GLboolean NormalizedFromFormat(const Format format) { switch (format) { case Format::R8G8_UNORM: FALLTHROUGH; case Format::R8G8B8_UNORM: FALLTHROUGH; case Format::R8G8B8A8_UNORM: FALLTHROUGH; case Format::R16_UNORM: FALLTHROUGH; case Format::R16G16_UNORM: return GL_TRUE; default: break; } return GL_FALSE; } int GetAttributeLocationFromStream( CDevice* device, const VertexAttributeStream stream) { // Old mapping makes sense only if we have an old/low-end hardware. Else we // need to use sequential numbering to fix #3054. We use presence of // compute shaders as a check that the hardware has universal CUs. if (device->GetCapabilities().computeShaders) { return static_cast(stream); } else { // Map known semantics onto the attribute locations documented by NVIDIA: // https://download.nvidia.com/developer/Papers/2005/OpenGL_2.0/NVIDIA_OpenGL_2.0_Support.pdf // https://developer.download.nvidia.com/opengl/glsl/glsl_release_notes.pdf switch (stream) { case VertexAttributeStream::POSITION: return 0; case VertexAttributeStream::NORMAL: return 2; case VertexAttributeStream::COLOR: return 3; case VertexAttributeStream::UV0: return 8; case VertexAttributeStream::UV1: return 9; case VertexAttributeStream::UV2: return 10; case VertexAttributeStream::UV3: return 11; case VertexAttributeStream::UV4: return 12; case VertexAttributeStream::UV5: return 13; case VertexAttributeStream::UV6: return 14; case VertexAttributeStream::UV7: return 15; } } debug_warn("Invalid attribute semantics"); return 0; } bool PreprocessShaderFile( bool arb, const CShaderDefines& defines, const VfsPath& path, CStr& source, std::vector& fileDependencies) { CVFSFile file; if (file.Load(g_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to load shader file: '%s'", path.string8()); return false; } CPreprocessorWrapper preprocessor( [arb, &fileDependencies](const CStr& includePath, CStr& out) -> bool { const VfsPath includeFilePath( (arb ? L"shaders/arb/" : L"shaders/glsl/") + wstring_from_utf8(includePath)); // Add dependencies anyway to reload the shader when the file is // appeared. fileDependencies.push_back(includeFilePath); CVFSFile includeFile; if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK) { LOGERROR("Failed to load shader include file: '%s'", includeFilePath.string8()); return false; } out = includeFile.GetAsString(); return true; }); preprocessor.AddDefines(defines); #if CONFIG2_GLES if (!arb) { // GLES defines the macro "GL_ES" in its GLSL preprocessor, // but since we run our own preprocessor first, we need to explicitly // define it here preprocessor.AddDefine("GL_ES", "1"); } #endif source = preprocessor.Preprocess(file.GetAsString()); return true; } #if !CONFIG2_GLES std::tuple GetElementTypeAndCountFromString(const CStr& str) { #define CASE(MATCH_STRING, TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \ if (str == MATCH_STRING) return {GL_ ## TYPE, GL_ ## ELEMENT_TYPE, ELEMENT_COUNT} CASE("float", FLOAT, FLOAT, 1); CASE("vec2", FLOAT_VEC2, FLOAT, 2); CASE("vec3", FLOAT_VEC3, FLOAT, 3); CASE("vec4", FLOAT_VEC4, FLOAT, 4); CASE("mat2", FLOAT_MAT2, FLOAT, 4); CASE("mat3", FLOAT_MAT3, FLOAT, 9); CASE("mat4", FLOAT_MAT4, FLOAT, 16); #if !CONFIG2_GLES // GL ES 2.0 doesn't support non-square matrices. CASE("mat2x3", FLOAT_MAT2x3, FLOAT, 6); CASE("mat2x4", FLOAT_MAT2x4, FLOAT, 8); CASE("mat3x2", FLOAT_MAT3x2, FLOAT, 6); CASE("mat3x4", FLOAT_MAT3x4, FLOAT, 12); CASE("mat4x2", FLOAT_MAT4x2, FLOAT, 8); CASE("mat4x3", FLOAT_MAT4x3, FLOAT, 12); #endif // A somewhat incomplete listing, missing "shadow" and "rect" versions // which are interpreted as 2D (NB: our shadowmaps may change // type based on user config). #if CONFIG2_GLES if (str == "sampler1D") debug_warn(L"sampler1D not implemented on GLES"); #else CASE("sampler1D", SAMPLER_1D, TEXTURE_1D, 1); #endif CASE("sampler2D", SAMPLER_2D, TEXTURE_2D, 1); #if CONFIG2_GLES if (str == "sampler2DShadow") debug_warn(L"sampler2DShadow not implemented on GLES"); if (str == "sampler3D") debug_warn(L"sampler3D not implemented on GLES"); #else CASE("sampler2DShadow", SAMPLER_2D_SHADOW, TEXTURE_2D, 1); CASE("sampler3D", SAMPLER_3D, TEXTURE_3D, 1); #endif CASE("samplerCube", SAMPLER_CUBE, TEXTURE_CUBE_MAP, 1); #undef CASE return {0, 0, 0}; } #endif // !CONFIG2_GLES } // anonymous namespace #if !CONFIG2_GLES class CShaderProgramARB final : public CShaderProgram { public: CShaderProgramARB( CDevice* device, const VfsPath& path, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath, const CShaderDefines& defines, const std::map>& vertexIndices, const std::map>& fragmentIndices, int streamflags) : CShaderProgram(streamflags), m_Device(device) { glGenProgramsARB(1, &m_VertexProgram); glGenProgramsARB(1, &m_FragmentProgram); std::vector newFileDependencies = {path, vertexFilePath, fragmentFilePath}; CStr vertexCode; if (!PreprocessShaderFile(true, defines, vertexFilePath, vertexCode, newFileDependencies)) return; CStr fragmentCode; if (!PreprocessShaderFile(true, defines, fragmentFilePath, fragmentCode, newFileDependencies)) return; m_FileDependencies = std::move(newFileDependencies); // TODO: replace by scoped bind. m_Device->GetActiveCommandContext()->SetGraphicsPipelineState( MakeDefaultGraphicsPipelineStateDesc()); if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, vertexFilePath, vertexCode)) return; if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, fragmentFilePath, fragmentCode)) return; for (const auto& index : vertexIndices) { BindingSlot& bindingSlot = GetOrCreateBindingSlot(index.first); bindingSlot.vertexProgramLocation = index.second.second; const auto [type, elementType, elementCount] = GetElementTypeAndCountFromString(index.second.first); bindingSlot.type = type; bindingSlot.elementType = elementType; bindingSlot.elementCount = elementCount; } for (const auto& index : fragmentIndices) { BindingSlot& bindingSlot = GetOrCreateBindingSlot(index.first); bindingSlot.fragmentProgramLocation = index.second.second; const auto [type, elementType, elementCount] = GetElementTypeAndCountFromString(index.second.first); if (bindingSlot.type && type != bindingSlot.type) { LOGERROR("CShaderProgramARB: vertex and fragment program uniforms with the same name should have the same type."); } bindingSlot.type = type; bindingSlot.elementType = elementType; bindingSlot.elementCount = elementCount; } } ~CShaderProgramARB() override { glDeleteProgramsARB(1, &m_VertexProgram); glDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); glBindProgramARB(target, program); ogl_WarnIfError(); glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); if (ogl_SquelchError(GL_INVALID_OPERATION)) { GLint errPos = 0; glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1; char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr); return false; } glBindProgramARB(target, 0); ogl_WarnIfError(); return true; } void Bind(CShaderProgram* previousShaderProgram) override { if (previousShaderProgram) previousShaderProgram->Unbind(); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); BindClientStates(); } void Unbind() override { glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); UnbindClientStates(); } IDevice* GetDevice() override { return m_Device; } int32_t GetBindingSlot(const CStrIntern name) const override { auto it = m_BindingSlotsMapping.find(name); return it == m_BindingSlotsMapping.end() ? -1 : it->second; } TextureUnit GetTextureUnit(const int32_t bindingSlot) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return { 0, 0, 0 }; TextureUnit textureUnit; textureUnit.type = m_BindingSlots[bindingSlot].type; textureUnit.target = m_BindingSlots[bindingSlot].elementType; textureUnit.unit = m_BindingSlots[bindingSlot].fragmentProgramLocation; return textureUnit; } void SetUniform( const int32_t bindingSlot, const float value) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected float)"); return; } SetUniform(m_BindingSlots[bindingSlot], value, 0.0f, 0.0f, 0.0f); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC2) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec2)"); return; } SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, 0.0f, 0.0f); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC3) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec3)"); return; } SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, valueZ, 0.0f); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC4) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec4)"); return; } SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, valueZ, valueW); } void SetUniform( const int32_t bindingSlot, PS::span values) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT) { LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform element type (expected float)"); return; } if (m_BindingSlots[bindingSlot].elementCount > static_cast(values.size())) { LOGERROR( "CShaderProgramARB::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu)", m_BindingSlots[bindingSlot].elementCount, values.size()); return; } const GLenum type = m_BindingSlots[bindingSlot].type; if (type == GL_FLOAT) SetUniform(m_BindingSlots[bindingSlot], values[0], 0.0f, 0.0f, 0.0f); else if (type == GL_FLOAT_VEC2) SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], 0.0f, 0.0f); else if (type == GL_FLOAT_VEC3) SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], values[2], 0.0f); else if (type == GL_FLOAT_VEC4) SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], values[2], values[3]); else if (type == GL_FLOAT_MAT4) SetUniformMatrix(m_BindingSlots[bindingSlot], values); else LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4)"); ogl_WarnIfError(); } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: struct BindingSlot { CStrIntern name; int vertexProgramLocation; int fragmentProgramLocation; GLenum type; GLenum elementType; GLint elementCount; }; BindingSlot& GetOrCreateBindingSlot(const CStrIntern name) { auto it = m_BindingSlotsMapping.find(name); if (it == m_BindingSlotsMapping.end()) { m_BindingSlotsMapping[name] = m_BindingSlots.size(); BindingSlot bindingSlot{}; bindingSlot.name = name; bindingSlot.vertexProgramLocation = -1; bindingSlot.fragmentProgramLocation = -1; bindingSlot.elementType = 0; bindingSlot.elementCount = 0; m_BindingSlots.emplace_back(std::move(bindingSlot)); return m_BindingSlots.back(); } else return m_BindingSlots[it->second]; } void SetUniform( const BindingSlot& bindingSlot, const float v0, const float v1, const float v2, const float v3) { SetUniform(GL_VERTEX_PROGRAM_ARB, bindingSlot.vertexProgramLocation, v0, v1, v2, v3); SetUniform(GL_FRAGMENT_PROGRAM_ARB, bindingSlot.fragmentProgramLocation, v0, v1, v2, v3); } void SetUniform( const GLenum target, const int location, const float v0, const float v1, const float v2, const float v3) { if (location >= 0) { glProgramLocalParameter4fARB( target, static_cast(location), v0, v1, v2, v3); } } void SetUniformMatrix( const BindingSlot& bindingSlot, PS::span values) { const size_t mat4ElementCount = 16; ENSURE(values.size() == mat4ElementCount); SetUniformMatrix(GL_VERTEX_PROGRAM_ARB, bindingSlot.vertexProgramLocation, values); SetUniformMatrix(GL_FRAGMENT_PROGRAM_ARB, bindingSlot.fragmentProgramLocation, values); } void SetUniformMatrix( const GLenum target, const int location, PS::span values) { if (location >= 0) { glProgramLocalParameter4fARB( target, static_cast(location + 0), values[0], values[4], values[8], values[12]); glProgramLocalParameter4fARB( target, static_cast(location + 1), values[1], values[5], values[9], values[13]); glProgramLocalParameter4fARB( target, static_cast(location + 2), values[2], values[6], values[10], values[14]); glProgramLocalParameter4fARB( target, static_cast(location + 3), values[3], values[7], values[11], values[15]); } } CDevice* m_Device = nullptr; std::vector m_FileDependencies; GLuint m_VertexProgram; GLuint m_FragmentProgram; std::vector m_BindingSlots; std::unordered_map m_BindingSlotsMapping; }; #endif // !CONFIG2_GLES class CShaderProgramGLSL final : public CShaderProgram { public: CShaderProgramGLSL( CDevice* device, const CStr& name, const VfsPath& path, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) : CShaderProgram(streamflags), m_Device(device), m_Name(name), m_VertexAttribs(vertexAttribs) { for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) m_ActiveVertexAttributes.emplace_back(it->second); std::sort(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end()); m_Program = 0; m_VertexShader = glCreateShader(GL_VERTEX_SHADER); m_FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); m_FileDependencies = {path, vertexFilePath, fragmentFilePath}; #if !CONFIG2_GLES if (m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_SHADER, m_VertexShader, -1, vertexFilePath.string8().c_str()); glObjectLabel(GL_SHADER, m_FragmentShader, -1, fragmentFilePath.string8().c_str()); } #endif std::vector newFileDependencies = {path, vertexFilePath, fragmentFilePath}; CStr vertexCode; if (!PreprocessShaderFile(false, defines, vertexFilePath, vertexCode, newFileDependencies)) return; CStr fragmentCode; if (!PreprocessShaderFile(false, defines, fragmentFilePath, fragmentCode, newFileDependencies)) return; m_FileDependencies = std::move(newFileDependencies); if (vertexCode.empty()) { LOGERROR("Failed to preprocess vertex shader: '%s'", vertexFilePath.string8()); return; } if (fragmentCode.empty()) { LOGERROR("Failed to preprocess fragment shader: '%s'", fragmentFilePath.string8()); return; } #if CONFIG2_GLES // Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00, // and also to set default float precision for fragment shaders vertexCode.Replace("#version 110\n", "#version 100\n"); vertexCode.Replace("#version 110\r\n", "#version 100\n"); vertexCode.Replace("#version 120\n", "#version 100\n"); vertexCode.Replace("#version 120\r\n", "#version 100\n"); fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n"); #endif // TODO: replace by scoped bind. m_Device->GetActiveCommandContext()->SetGraphicsPipelineState( MakeDefaultGraphicsPipelineStateDesc()); if (!Compile(m_VertexShader, vertexFilePath, vertexCode)) return; if (!Compile(m_FragmentShader, fragmentFilePath, fragmentCode)) return; if (!Link(vertexFilePath, fragmentFilePath)) return; } ~CShaderProgramGLSL() override { if (m_Program) glDeleteProgram(m_Program); glDeleteShader(m_VertexShader); glDeleteShader(m_FragmentShader); } bool Compile(GLuint shader, const VfsPath& file, const CStr& code) { const char* code_string = code.c_str(); GLint code_length = code.length(); glShaderSource(shader, 1, &code_string, &code_length); ogl_WarnIfError(); glCompileShader(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); // Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0 // (http://code.google.com/p/android/issues/detail?id=9953) if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetShaderInfoLog(shader, length, NULL, infolog); if (ok) LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog); else LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); return ok; } bool Link(const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath) { ENSURE(!m_Program); m_Program = glCreateProgram(); #if !CONFIG2_GLES if (m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_PROGRAM, m_Program, -1, m_Name.c_str()); } #endif glAttachShader(m_Program, m_VertexShader); ogl_WarnIfError(); glAttachShader(m_Program, m_FragmentShader); ogl_WarnIfError(); // Set up the attribute bindings explicitly, since apparently drivers // don't always pick the most efficient bindings automatically, // and also this lets us hardcode indexes into VertexPointer etc for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) glBindAttribLocation(m_Program, it->second, it->first.c_str()); glLinkProgram(m_Program); GLint ok = 0; glGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetProgramInfoLog(m_Program, length, NULL, infolog); if (ok) LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", vertexFilePath.string8(), fragmentFilePath.string8(), infolog); else LOGERROR("Failed to link program '%s'+'%s':\n%s", vertexFilePath.string8(), fragmentFilePath.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); if (!ok) return false; Bind(nullptr); ogl_WarnIfError(); // Reorder sampler units to decrease redundant texture unit changes when // samplers bound in a different order. const std::unordered_map requiredUnits = { {CStrIntern("baseTex"), 0}, {CStrIntern("normTex"), 1}, {CStrIntern("specTex"), 2}, {CStrIntern("aoTex"), 3}, {CStrIntern("shadowTex"), 4}, {CStrIntern("losTex"), 5}, }; std::vector occupiedUnits; GLint numUniforms = 0; glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms); ogl_WarnIfError(); for (GLint i = 0; i < numUniforms; ++i) { // TODO: use GL_ACTIVE_UNIFORM_MAX_LENGTH for the size. char name[256] = {0}; GLsizei nameLength = 0; GLint size = 0; GLenum type = 0; glGetActiveUniform(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); const GLint location = glGetUniformLocation(m_Program, name); // OpenGL specification is a bit vague about a name returned by glGetActiveUniform. // NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without; while (nameLength > 3 && name[nameLength - 3] == '[' && name[nameLength - 2] == '0' && name[nameLength - 1] == ']') { nameLength -= 3; } name[nameLength] = 0; const CStrIntern nameIntern(name); m_BindingSlotsMapping[nameIntern] = m_BindingSlots.size(); BindingSlot bindingSlot{}; bindingSlot.name = nameIntern; bindingSlot.location = location; bindingSlot.size = size; bindingSlot.type = type; bindingSlot.isTexture = false; #define CASE(TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \ case GL_ ## TYPE: \ bindingSlot.elementType = GL_ ## ELEMENT_TYPE; \ bindingSlot.elementCount = ELEMENT_COUNT; \ break; switch (type) { CASE(FLOAT, FLOAT, 1); CASE(FLOAT_VEC2, FLOAT, 2); CASE(FLOAT_VEC3, FLOAT, 3); CASE(FLOAT_VEC4, FLOAT, 4); CASE(INT, INT, 1); CASE(FLOAT_MAT2, FLOAT, 4); CASE(FLOAT_MAT3, FLOAT, 9); CASE(FLOAT_MAT4, FLOAT, 16); #if !CONFIG2_GLES // GL ES 2.0 doesn't support non-square matrices. CASE(FLOAT_MAT2x3, FLOAT, 6); CASE(FLOAT_MAT2x4, FLOAT, 8); CASE(FLOAT_MAT3x2, FLOAT, 6); CASE(FLOAT_MAT3x4, FLOAT, 12); CASE(FLOAT_MAT4x2, FLOAT, 8); CASE(FLOAT_MAT4x3, FLOAT, 12); #endif } #undef CASE // Assign sampler uniforms to sequential texture units. if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE #if !CONFIG2_GLES || type == GL_SAMPLER_2D_SHADOW #endif ) { const auto it = requiredUnits.find(nameIntern); const int unit = it == requiredUnits.end() ? -1 : it->second; bindingSlot.elementType = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D); bindingSlot.elementCount = unit; bindingSlot.isTexture = true; if (unit != -1) { if (unit >= static_cast(occupiedUnits.size())) occupiedUnits.resize(unit + 1); occupiedUnits[unit] = true; } } if (bindingSlot.elementType == 0) { LOGERROR("CShaderProgramGLSL::Link: unsupported uniform type: 0x%04x", static_cast(type)); } m_BindingSlots.emplace_back(std::move(bindingSlot)); } for (BindingSlot& bindingSlot : m_BindingSlots) { if (!bindingSlot.isTexture) continue; if (bindingSlot.elementCount == -1) { // We need to find a minimal available unit. int unit = 0; while (unit < static_cast(occupiedUnits.size()) && occupiedUnits[unit]) ++unit; if (unit >= static_cast(occupiedUnits.size())) occupiedUnits.resize(unit + 1); occupiedUnits[unit] = true; bindingSlot.elementCount = unit; } // Link uniform to unit. glUniform1i(bindingSlot.location, bindingSlot.elementCount); ogl_WarnIfError(); } // TODO: verify that we're not using more samplers than is supported Unbind(); return true; } void Bind(CShaderProgram* previousShaderProgram) override { CShaderProgramGLSL* previousShaderProgramGLSL = nullptr; if (previousShaderProgram) previousShaderProgramGLSL = static_cast(previousShaderProgram); ENSURE(this != previousShaderProgramGLSL); glUseProgram(m_Program); if (previousShaderProgramGLSL) { std::vector::iterator itPrevious = previousShaderProgramGLSL->m_ActiveVertexAttributes.begin(); std::vector::iterator itNext = m_ActiveVertexAttributes.begin(); while ( itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() || itNext != m_ActiveVertexAttributes.end()) { if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() && itNext != m_ActiveVertexAttributes.end()) { if (*itPrevious == *itNext) { ++itPrevious; ++itNext; } else if (*itPrevious < *itNext) { glDisableVertexAttribArray(*itPrevious); ++itPrevious; } else if (*itPrevious > *itNext) { glEnableVertexAttribArray(*itNext); ++itNext; } } else if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end()) { glDisableVertexAttribArray(*itPrevious); ++itPrevious; } else if (itNext != m_ActiveVertexAttributes.end()) { glEnableVertexAttribArray(*itNext); ++itNext; } } } else { for (const int index : m_ActiveVertexAttributes) glEnableVertexAttribArray(index); } m_ValidStreams = 0; } void Unbind() override { glUseProgram(0); for (const int index : m_ActiveVertexAttributes) glDisableVertexAttribArray(index); } IDevice* GetDevice() override { return m_Device; } int32_t GetBindingSlot(const CStrIntern name) const override { auto it = m_BindingSlotsMapping.find(name); return it == m_BindingSlotsMapping.end() ? -1 : it->second; } TextureUnit GetTextureUnit(const int32_t bindingSlot) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return { 0, 0, 0 }; TextureUnit textureUnit; textureUnit.type = m_BindingSlots[bindingSlot].type; textureUnit.target = m_BindingSlots[bindingSlot].elementType; textureUnit.unit = m_BindingSlots[bindingSlot].elementCount; return textureUnit; } void SetUniform( const int32_t bindingSlot, const float value) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform1f(m_BindingSlots[bindingSlot].location, value); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC2 || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec2) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform2f(m_BindingSlots[bindingSlot].location, valueX, valueY); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC3 || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec3) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform3f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC4 || m_BindingSlots[bindingSlot].size != 1) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec4) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } glUniform4f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ, valueW); ogl_WarnIfError(); } void SetUniform( const int32_t bindingSlot, PS::span values) override { if (bindingSlot < 0 || bindingSlot >= static_cast(m_BindingSlots.size())) return; if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT) { LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform element type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str()); return; } if (m_BindingSlots[bindingSlot].size == 1 && m_BindingSlots[bindingSlot].elementCount > static_cast(values.size())) { LOGERROR( "CShaderProgramGLSL::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu) '%s'", m_BindingSlots[bindingSlot].elementCount, values.size(), m_BindingSlots[bindingSlot].name.c_str()); return; } const GLint location = m_BindingSlots[bindingSlot].location; const GLenum type = m_BindingSlots[bindingSlot].type; if (type == GL_FLOAT) glUniform1fv(location, 1, values.data()); else if (type == GL_FLOAT_VEC2) glUniform2fv(location, 1, values.data()); else if (type == GL_FLOAT_VEC3) glUniform3fv(location, 1, values.data()); else if (type == GL_FLOAT_VEC4) glUniform4fv(location, 1, values.data()); else if (type == GL_FLOAT_MAT4) { // For case of array of matrices we might pass less number of matrices. const GLint size = std::min( m_BindingSlots[bindingSlot].size, static_cast(values.size() / 16)); glUniformMatrix4fv(location, size, GL_FALSE, values.data()); } else LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4) '%s'", m_BindingSlots[bindingSlot].name.c_str()); ogl_WarnIfError(); } void VertexAttribPointer( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, const VertexAttributeRate rate, const void* data) override { const int attributeLocation = GetAttributeLocationFromStream(m_Device, stream); std::vector::const_iterator it = std::lower_bound(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end(), attributeLocation); if (it == m_ActiveVertexAttributes.end() || *it != attributeLocation) return; const GLint size = GLSizeFromFormat(format); const GLenum type = GLTypeFromFormat(format); const GLboolean normalized = NormalizedFromFormat(format); glVertexAttribPointer( attributeLocation, size, type, normalized, stride, static_cast(data) + offset); +#if CONFIG2_GLES + ENSURE(!m_Device->GetCapabilities().instancing); + UNUSED2(rate); +#else if (rate == VertexAttributeRate::PER_INSTANCE) ENSURE(m_Device->GetCapabilities().instancing); if (m_Device->GetCapabilities().instancing) { glVertexAttribDivisorARB(attributeLocation, rate == VertexAttributeRate::PER_INSTANCE ? 1 : 0); } +#endif m_ValidStreams |= GetStreamMask(stream); } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: CDevice* m_Device = nullptr; CStr m_Name; std::vector m_FileDependencies; std::map m_VertexAttribs; // Sorted list of active vertex attributes. std::vector m_ActiveVertexAttributes; GLuint m_Program; GLuint m_VertexShader, m_FragmentShader; struct BindingSlot { CStrIntern name; GLint location; GLint size; GLenum type; GLenum elementType; GLint elementCount; bool isTexture; }; std::vector m_BindingSlots; std::unordered_map m_BindingSlotsMapping; }; CShaderProgram::CShaderProgram(int streamflags) : m_StreamFlags(streamflags), m_ValidStreams(0) { } CShaderProgram::~CShaderProgram() = default; // static std::unique_ptr CShaderProgram::Create(CDevice* device, const CStr& name, const CShaderDefines& baseDefines) { PROFILE2("loading shader"); PROFILE2_ATTR("name: %s", name.c_str()); VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return nullptr; #if USE_SHADER_XML_VALIDATION { // Serialize the XMB data and pass it to the validator XMLWriter_File shaderFile; shaderFile.SetPrettyPrint(false); shaderFile.XMB(XeroFile); bool ok = CXeromyces::ValidateEncoded("shader", name, shaderFile.GetOutput()); if (!ok) return nullptr; } #endif // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(define); EL(fragment); EL(stream); EL(uniform); EL(vertex); AT(attribute); AT(file); AT(if); AT(loc); AT(name); AT(type); AT(value); #undef AT #undef EL CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement root = XeroFile.GetRoot(); const bool isGLSL = root.GetAttributes().GetNamedItem(at_type) == "glsl"; VfsPath vertexFile; VfsPath fragmentFile; CShaderDefines defines = baseDefines; std::map> vertexUniforms; std::map> fragmentUniforms; std::map vertexAttribs; int streamFlags = 0; XERO_ITER_EL(root, child) { if (child.GetNodeName() == el_define) { defines.Add(CStrIntern(child.GetAttributes().GetNamedItem(at_name)), CStrIntern(child.GetAttributes().GetNamedItem(at_value))); } else if (child.GetNodeName() == el_vertex) { vertexFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(child, param) { XMBAttributeList attributes = param.GetAttributes(); CStr cond = attributes.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (param.GetNodeName() == el_uniform) { vertexUniforms[CStrIntern(attributes.GetNamedItem(at_name))] = std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt()); } else if (param.GetNodeName() == el_stream) { const CStr streamName = attributes.GetNamedItem(at_name); const CStr attributeName = attributes.GetNamedItem(at_attribute); if (attributeName.empty() && isGLSL) LOGERROR("Empty attribute name in vertex shader description '%s'", vertexFile.string8().c_str()); VertexAttributeStream stream = VertexAttributeStream::UV7; if (streamName == "pos") stream = VertexAttributeStream::POSITION; else if (streamName == "normal") stream = VertexAttributeStream::NORMAL; else if (streamName == "color") stream = VertexAttributeStream::COLOR; else if (streamName == "uv0") stream = VertexAttributeStream::UV0; else if (streamName == "uv1") stream = VertexAttributeStream::UV1; else if (streamName == "uv2") stream = VertexAttributeStream::UV2; else if (streamName == "uv3") stream = VertexAttributeStream::UV3; else if (streamName == "uv4") stream = VertexAttributeStream::UV4; else if (streamName == "uv5") stream = VertexAttributeStream::UV5; else if (streamName == "uv6") stream = VertexAttributeStream::UV6; else if (streamName == "uv7") stream = VertexAttributeStream::UV7; else LOGERROR("Unknown stream '%s' in vertex shader description '%s'", streamName.c_str(), vertexFile.string8().c_str()); if (isGLSL) { const int attributeLocation = GetAttributeLocationFromStream(device, stream); vertexAttribs[CStrIntern(attributeName)] = attributeLocation; } streamFlags |= GetStreamMask(stream); } } } else if (child.GetNodeName() == el_fragment) { fragmentFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(child, param) { XMBAttributeList attributes = param.GetAttributes(); CStr cond = attributes.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (param.GetNodeName() == el_uniform) { fragmentUniforms[CStrIntern(attributes.GetNamedItem(at_name))] = std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt()); } } } } if (isGLSL) { return std::make_unique( device, name, xmlFilename, vertexFile, fragmentFile, defines, vertexAttribs, streamFlags); } else { #if CONFIG2_GLES LOGERROR("CShaderProgram::Create: '%s'+'%s': ARB shaders not supported on this device", vertexFile.string8(), fragmentFile.string8()); return nullptr; #else return std::make_unique( device, xmlFilename, vertexFile, fragmentFile, defines, vertexUniforms, fragmentUniforms, streamFlags); #endif } } // These should all be overridden by CShaderProgramGLSL, and not used // if a non-GLSL shader was loaded instead: #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL // (GLES doesn't support any other types of shader program): void CShaderProgram::VertexPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::VertexPointer should be overridden"); } void CShaderProgram::NormalPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::NormalPointer should be overridden"); } void CShaderProgram::ColorPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::ColorPointer should be overridden"); } void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::TexCoordPointer should be overridden"); } #else // These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders // both use the fixed-function vertex attribute pointers so we'll share their // definitions here: void CShaderProgram::VertexPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { const GLint size = GLSizeFromFormat(format); ENSURE(2 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glVertexPointer(size, type, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::POSITION); } void CShaderProgram::NormalPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { ENSURE(format == Renderer::Backend::Format::R32G32B32_SFLOAT); glNormalPointer(GL_FLOAT, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::NORMAL); } void CShaderProgram::ColorPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { const GLint size = GLSizeFromFormat(format); ENSURE(3 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glColorPointer(size, type, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::COLOR); } void CShaderProgram::TexCoordPointer(GLenum texture, const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { glClientActiveTextureARB(texture); const GLint size = GLSizeFromFormat(format); ENSURE(1 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glTexCoordPointer(size, type, stride, pointer); glClientActiveTextureARB(GL_TEXTURE0); m_ValidStreams |= GetStreamMask(VertexAttributeStream::UV0) << (texture - GL_TEXTURE0); } void CShaderProgram::BindClientStates() { ENSURE(m_StreamFlags == (m_StreamFlags & ( GetStreamMask(VertexAttributeStream::POSITION) | GetStreamMask(VertexAttributeStream::NORMAL) | GetStreamMask(VertexAttributeStream::COLOR) | GetStreamMask(VertexAttributeStream::UV0) | GetStreamMask(VertexAttributeStream::UV1)))); // Enable all the desired client states for non-GLSL rendering if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION)) glEnableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL)) glEnableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR)) glEnableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0)) { glClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1)) { glClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } // Rendering code must subsequently call VertexPointer etc for all of the streams // that were activated in this function, else AssertPointersBound will complain // that some arrays were unspecified m_ValidStreams = 0; } void CShaderProgram::UnbindClientStates() { if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION)) glDisableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL)) glDisableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR)) glDisableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0)) { glClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1)) { glClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES bool CShaderProgram::IsStreamActive(const VertexAttributeStream stream) const { return (m_StreamFlags & GetStreamMask(stream)) != 0; } void CShaderProgram::VertexAttribPointer( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, const VertexAttributeRate rate, const void* data) { ENSURE(rate == VertexAttributeRate::PER_VERTEX); switch (stream) { case VertexAttributeStream::POSITION: VertexPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::NORMAL: NormalPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::COLOR: ColorPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::UV0: FALLTHROUGH; case VertexAttributeStream::UV1: FALLTHROUGH; case VertexAttributeStream::UV2: FALLTHROUGH; case VertexAttributeStream::UV3: FALLTHROUGH; case VertexAttributeStream::UV4: FALLTHROUGH; case VertexAttributeStream::UV5: FALLTHROUGH; case VertexAttributeStream::UV6: FALLTHROUGH; case VertexAttributeStream::UV7: { const int indexOffset = static_cast(stream) - static_cast(VertexAttributeStream::UV0); TexCoordPointer(GL_TEXTURE0 + indexOffset, format, stride, static_cast(data) + offset); break; } default: debug_warn("Unsupported stream"); }; } void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 26906) @@ -1,169 +1,175 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "lib/external_libraries/libsdl.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" #if SDL_VERSION_ATLEAST(2, 0, 8) #include #endif namespace Renderer { namespace Backend { namespace Vulkan { // static std::unique_ptr CDevice::Create(SDL_Window* UNUSED(window)) { std::unique_ptr device(new CDevice()); return device; } CDevice::CDevice() = default; CDevice::~CDevice() = default; void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { Script::SetProperty(rq, settings, "name", "vulkan"); std::string vulkanSupport = "unsupported"; // According to http://wiki.libsdl.org/SDL_Vulkan_LoadLibrary the following // functionality is supported since SDL 2.0.8. #if SDL_VERSION_ATLEAST(2, 0, 8) if (!SDL_Vulkan_LoadLibrary(nullptr)) { void* vkGetInstanceProcAddr = SDL_Vulkan_GetVkGetInstanceProcAddr(); if (vkGetInstanceProcAddr) vulkanSupport = "supported"; else vulkanSupport = "noprocaddr"; SDL_Vulkan_UnloadLibrary(); } else { vulkanSupport = "cantload"; } #endif Script::SetProperty(rq, settings, "status", vulkanSupport); } IFramebuffer* CDevice::GetCurrentBackbuffer() { return nullptr; } std::unique_ptr CDevice::CreateCommandContext() { return nullptr; } std::unique_ptr CDevice::CreateTexture(const char* name, const ITexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { UNUSED2(name); UNUSED2(type); UNUSED2(format); UNUSED2(width); UNUSED2(height); UNUSED2(defaultSamplerDesc); UNUSED2(MIPLevelCount); UNUSED2(sampleCount); return nullptr; } std::unique_ptr CDevice::CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { UNUSED2(name); UNUSED2(format); UNUSED2(width); UNUSED2(height); UNUSED2(defaultSamplerDesc); UNUSED2(MIPLevelCount); UNUSED2(sampleCount); return nullptr; } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) { UNUSED2(name); UNUSED2(colorAttachment); UNUSED2(depthStencilAttachment); return nullptr; } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) { UNUSED2(name); UNUSED2(colorAttachment); UNUSED2(depthStencilAttachment); UNUSED2(clearColor); return nullptr; } std::unique_ptr CDevice::CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { UNUSED2(name); UNUSED2(type); UNUSED2(size); UNUSED2(dynamic); return nullptr; } std::unique_ptr CDevice::CreateShaderProgram( const CStr& name, const CShaderDefines& defines) { UNUSED2(name); UNUSED2(defines); return nullptr; } void CDevice::Present() { } bool CDevice::IsTextureFormatSupported(const Format format) const { UNUSED2(format); return false; } +bool CDevice::IsFramebufferFormatSupported(const Format format) const +{ + UNUSED2(format); + return false; +} + } // namespace Vulkan } // namespace Backend } // namespace Renderer Index: ps/trunk/binaries/data/mods/public/shaders/arb/minimap.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/minimap.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/minimap.fp (revision 26906) @@ -1,42 +1,41 @@ !!ARBfp1.0 #if MINIMAP_MASK ATTRIB v_maskUV = fragment.texcoord[1]; TEMP mask; TEX mask, v_maskUV, texture[2], 2D; #endif #if MINIMAP_BASE #if MINIMAP_MASK TEMP color; TEX color, fragment.texcoord[0], texture[0], 2D; MUL color.a, color.a, mask.a; MOV result.color, color; #else TEX result.color, fragment.texcoord[0], texture[0], 2D; #endif #endif #if MINIMAP_LOS TEMP tex; TEX tex, fragment.texcoord[0], texture[0], 2D; - SUB tex.a, 1.0, tex.a; - MOV result.color.r, 0.0; - MOV result.color.g, 0.0; - MOV result.color.b, 0.0; - MOV result.color.a, tex.a; + MOV result.color.r, tex.r; + MOV result.color.g, tex.r; + MOV result.color.b, tex.r; + MOV result.color.a, tex.r; #endif #if MINIMAP_POINT MOV result.color, fragment.color; MOV result.color.a, 1.0; #endif #if MINIMAP_LINE PARAM color = program.local[1]; MOV result.color, color; #endif END Index: ps/trunk/binaries/data/mods/public/shaders/arb/model_common.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/model_common.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/model_common.fp (revision 26906) @@ -1,157 +1,157 @@ !!ARBfp1.0 #if USE_FP_SHADOW OPTION ARB_fragment_program_shadow; #endif ATTRIB v_tex = fragment.texcoord[0]; ATTRIB v_shadow = fragment.texcoord[1]; ATTRIB v_los = fragment.texcoord[2]; #if USE_OBJECTCOLOR PARAM objectColor = program.local[0]; #else #if USE_PLAYERCOLOR PARAM playerColor = program.local[0]; #endif #endif PARAM shadingColor = program.local[1]; PARAM ambient = program.local[2]; #if USE_FP_SHADOW && USE_SHADOW_PCF PARAM shadowScale = program.local[3]; TEMP offset, size, weight, depthSample; #endif #if USE_SPECULAR ATTRIB v_normal = fragment.texcoord[3]; ATTRIB v_half = fragment.texcoord[4]; PARAM specularPower = program.local[4]; PARAM specularColor = program.local[5]; PARAM sunColor = program.local[6]; #endif TEMP tex; TEMP texdiffuse; TEMP sundiffuse; TEMP temp; TEMP color; TEMP shadow; TEX tex, v_tex, texture[0], 2D; // Alpha-test as early as possible #ifdef REQUIRE_ALPHA_GEQUAL SUB temp.x, tex.a, REQUIRE_ALPHA_GEQUAL; KIL temp.x; // discard if < 0.0 #endif #if USE_TRANSPARENT MOV result.color.a, tex; #endif // Apply coloring based on texture alpha #if USE_OBJECTCOLOR LRP temp.rgb, objectColor, 1.0, tex.a; MUL texdiffuse.rgb, tex, temp; #else #if USE_PLAYERCOLOR LRP temp.rgb, playerColor, 1.0, tex.a; MUL texdiffuse.rgb, tex, temp; #else MOV texdiffuse.rgb, tex; #endif #endif #if USE_SPECULAR // specular = sunColor * specularColor * pow(max(0.0, dot(normalize(v_normal), v_half)), specularPower); TEMP specular; TEMP normal; MUL specular.rgb, specularColor, sunColor; DP3 normal.w, v_normal, v_normal; RSQ normal.w, normal.w; MUL normal.xyz, v_normal, normal.w; DP3_SAT temp.y, normal, v_half; // temp^p = (2^lg2(temp))^p = 2^(lg2(temp)*p) LG2 temp.y, temp.y; MUL temp.y, temp.y, specularPower.x; EX2 temp.y, temp.y; // TODO: why not just use POW here? (should test performance first) MUL specular.rgb, specular, temp.y; #endif // color = (texdiffuse * sundiffuse + specular) * get_shadow() + texdiffuse * ambient; // (sundiffuse is 2*fragment.color due to clamp-avoidance in the vertex program) #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS TEMP shadowBias; TEMP biasedShdw; MOV shadowBias.x, 0.003; MOV biasedShdw, v_shadow; SUB biasedShdw.z, v_shadow.z, shadowBias.x; #if USE_FP_SHADOW #if USE_SHADOW_PCF SUB offset.xy, v_shadow, 0.5; FRC offset.xy, offset; ADD size.xy, offset, 1.0; SUB size.zw, 2.0, offset.xyxy; MAD offset.xy, -0.5, offset, v_shadow; MOV offset.z, biasedShdw.z; ADD weight, { 1.0, 1.0, -0.5, -0.5 }, offset.xyxy; MUL weight, weight, shadowScale.zwzw; MOV offset.xy, weight.zwww; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.x, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.y, depthSample.r; MOV offset.xy, weight.zyyy; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.z, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.w, depthSample.r; MUL size, size.zxzx, size.wwyy; DP4 shadow.x, temp, size; MUL shadow.x, shadow.x, 0.111111; #else TEX shadow.x, biasedShdw, texture[1], SHADOW2D; #endif #else TEX tex, v_shadow, texture[1], 2D; MOV_SAT temp.z, biasedShdw.z; SGE shadow.x, tex.x, temp.z; #endif MUL sundiffuse.rgb, fragment.color, 2.0; #if USE_SPECULAR MAD color.rgb, texdiffuse, sundiffuse, specular; MUL temp.rgb, texdiffuse, ambient; MAD color.rgb, color, shadow.x, temp; #else MAD temp.rgb, sundiffuse, shadow.x, ambient; MUL color.rgb, texdiffuse, temp; #endif #else #if USE_SPECULAR MAD temp.rgb, fragment.color, 2.0, ambient; MAD color.rgb, texdiffuse, temp, specular; #else MAD temp.rgb, fragment.color, 2.0, ambient; MUL color.rgb, texdiffuse, temp; #endif #endif #if !IGNORE_LOS // Multiply everything by the LOS texture - TEX tex.a, v_los, texture[2], 2D; - MUL color.rgb, color, tex.a; + TEX tex.r, v_los, texture[2], 2D; + MUL color.rgb, color, tex.r; #endif MUL result.color.rgb, color, shadingColor; END Index: ps/trunk/binaries/data/mods/public/shaders/arb/overlayline.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/overlayline.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/overlayline.fp (revision 26906) @@ -1,32 +1,32 @@ !!ARBfp1.0 PARAM objectColor = program.local[0]; TEMP base; TEMP mask; TEMP color; // Combine base texture and color, using mask texture TEX base, fragment.texcoord[0], texture[0], 2D; TEX mask, fragment.texcoord[0], texture[1], 2D; #if USE_OBJECTCOLOR LRP color.rgb, mask, objectColor, base; #else LRP color.rgb, mask, fragment.color, base; #endif #if IGNORE_LOS MOV result.color.rgb, color; #else - // Multiply RGB by LOS texture (alpha channel) + // Multiply RGB by LOS texture (red channel) TEMP los; TEX los, fragment.texcoord[1], texture[2], 2D; - MUL result.color.rgb, color, los.a; + MUL result.color.rgb, color, los.r; #endif // Use alpha from base texture, combined with the object color/fragment alpha. #if USE_OBJECTCOLOR MUL result.color.a, objectColor.a, base.a; #else MUL result.color.a, fragment.color.a, base.a; #endif END Index: ps/trunk/binaries/data/mods/public/shaders/arb/particle.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/particle.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/particle.fp (revision 26906) @@ -1,23 +1,23 @@ !!ARBfp1.0 ATTRIB v_los = fragment.texcoord[1]; PARAM sunColor = program.local[0]; TEMP tex, losTex, color; TEX tex, fragment.texcoord[0], texture[0], 2D; TEMP temp; MOV temp, 0.5; ADD color.rgb, fragment.color, sunColor; MUL color.rgb, color, temp; MUL color.rgb, color, tex; // Multiply everything by the LOS texture TEX losTex, v_los, texture[1], 2D; -MUL result.color.rgb, color, losTex.a; +MUL result.color.rgb, color, losTex.r; MUL result.color.a, tex, fragment.color; END Index: ps/trunk/binaries/data/mods/public/shaders/arb/terrain_common.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/terrain_common.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/terrain_common.fp (revision 26906) @@ -1,99 +1,99 @@ !!ARBfp1.0 #if USE_FP_SHADOW OPTION ARB_fragment_program_shadow; #endif PARAM ambient = program.local[0]; #if DECAL PARAM shadingColor = program.local[1]; #endif #if USE_FP_SHADOW && USE_SHADOW_PCF PARAM shadowScale = program.local[2]; TEMP offset, size, weight, depthSample; #endif TEMP tex; TEMP temp; TEMP diffuse; TEMP color; #if BLEND // Use alpha from blend texture // TODO: maybe we should invert the texture instead of doing SUB here? TEX tex.a, fragment.texcoord[1], texture[1], 2D; SUB result.color.a, 1.0, tex.a; #endif // Load diffuse color TEX color, fragment.texcoord[0], texture[0], 2D; #if DECAL // Use alpha from main texture MOV result.color.a, color; #endif // Compute color = texture * (ambient + diffuse*shadow) // (diffuse is 2*fragment.color due to clamp-avoidance in the vertex program) #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS TEMP shadowBias; TEMP biasedShdw; MOV shadowBias.x, 0.0005; MOV biasedShdw, fragment.texcoord[2]; SUB biasedShdw.z, fragment.texcoord[2].z, shadowBias.x; #if USE_FP_SHADOW #if USE_SHADOW_PCF SUB offset.xy, fragment.texcoord[2], 0.5; FRC offset.xy, offset; ADD size.xy, offset, 1.0; SUB size.zw, 2.0, offset.xyxy; MAD offset.xy, -0.5, offset, fragment.texcoord[2]; MOV offset.z, biasedShdw.z; ADD weight, { 1.0, 1.0, -0.5, -0.5 }, offset.xyxy; MUL weight, weight, shadowScale.zwzw; MOV offset.xy, weight.zwww; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.x, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.y, depthSample.r; MOV offset.xy, weight.zyyy; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.z, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.w, depthSample.r; MUL size, size.zxzx, size.wwyy; DP4 temp.x, temp, size; MUL temp.x, temp.x, 0.111111; #else TEX temp.x, biasedShdw, texture[2], SHADOW2D; #endif #else TEX tex, fragment.texcoord[2], texture[2], 2D; MOV_SAT temp.z, biasedShdw.z; SGE temp.x, tex.x, temp.z; #endif MUL diffuse.rgb, fragment.color, 2.0; MAD temp.rgb, diffuse, temp.x, ambient; MUL color.rgb, color, temp; #else MAD temp.rgb, fragment.color, 2.0, ambient; MUL color.rgb, color, temp; #endif // Multiply everything by the LOS texture -TEX tex.a, fragment.texcoord[3], texture[3], 2D; -MUL color.rgb, color, tex.a; +TEX tex.r, fragment.texcoord[3], texture[3], 2D; +MUL color.rgb, color, tex.r; #if DECAL MUL result.color.rgb, color, shadingColor; #else MOV result.color.rgb, color; #endif END Index: ps/trunk/binaries/data/mods/public/shaders/arb/water_simple.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/water_simple.fp (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/arb/water_simple.fp (revision 26906) @@ -1,17 +1,17 @@ !!ARBfp1.0 PARAM color = program.local[2]; ATTRIB v_coords = fragment.texcoord[0]; ATTRIB v_losCoords = fragment.texcoord[1]; TEMP diffuse; TEX diffuse, v_coords, texture[0], 2D; MUL diffuse, diffuse, color; TEMP los; TEX los, v_losCoords, texture[1], 2D; -MUL diffuse, diffuse, los.a; +MUL diffuse, diffuse, los.r; MOV result.color, diffuse; END Index: ps/trunk/binaries/data/mods/public/shaders/effects/minimap_los.xml =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/effects/minimap_los.xml (nonexistent) +++ ps/trunk/binaries/data/mods/public/shaders/effects/minimap_los.xml (revision 26906) @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + Property changes on: ps/trunk/binaries/data/mods/public/shaders/effects/minimap_los.xml ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/shaders/glsl/common/los_fragment.h =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/common/los_fragment.h (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/common/los_fragment.h (revision 26906) @@ -1,21 +1,21 @@ #ifndef INCLUDED_LOS_FRAGMENT #define INCLUDED_LOS_FRAGMENT #if !IGNORE_LOS uniform sampler2D losTex; varying vec2 v_los; #endif float getLOS() { #if !IGNORE_LOS - float los = texture2D(losTex, v_los).a; + float los = texture2D(losTex, v_los).r; float threshold = 0.03; return (los - threshold) / (1.0 - threshold); #else return 1.0; #endif } #endif // INCLUDED_LOS_FRAGMENT Index: ps/trunk/binaries/data/mods/public/shaders/glsl/los_interp.fs =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/los_interp.fs (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/los_interp.fs (revision 26906) @@ -1,17 +1,17 @@ #version 110 varying vec2 v_tex; uniform sampler2D losTex1, losTex2; uniform float delta; void main(void) { - float los2 = texture2D(losTex1, v_tex).a; - float los1 = texture2D(losTex2, v_tex).a; + vec4 los2 = texture2D(losTex1, v_tex).rrrr; + vec4 los1 = texture2D(losTex2, v_tex).rrrr; - gl_FragColor.a = mix(los1, los2, clamp(delta, 0.0, 1.0)); + gl_FragColor = mix(los1, los2, clamp(delta, 0.0, 1.0)); } Index: ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.fs =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.fs (revision 26905) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/minimap.fs (revision 26906) @@ -1,48 +1,48 @@ #version 110 #if MINIMAP_BASE || MINIMAP_LOS uniform sampler2D baseTex; varying vec2 v_tex; #endif #if MINIMAP_MASK uniform sampler2D maskTex; varying vec2 v_maskUV; #endif #if MINIMAP_POINT varying vec3 color; #endif #if MINIMAP_LINE uniform vec4 color; #endif void main() { #if MINIMAP_MASK float mask = texture2D(maskTex, v_maskUV).a; #endif #if MINIMAP_BASE #if MINIMAP_MASK vec4 color = texture2D(baseTex, v_tex); gl_FragColor.rgb = color.rgb; gl_FragColor.a = color.a * mask; #else gl_FragColor = texture2D(baseTex, v_tex); #endif #endif #if MINIMAP_LOS - gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0 - texture2D(baseTex, v_tex).a); + gl_FragColor = texture2D(baseTex, v_tex).rrrr; #endif #if MINIMAP_POINT gl_FragColor = vec4(color, 1.0); #endif #if MINIMAP_LINE gl_FragColor = color; #endif } Index: ps/trunk/source/graphics/LOSTexture.cpp =================================================================== --- ps/trunk/source/graphics/LOSTexture.cpp (revision 26905) +++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26906) @@ -1,444 +1,470 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "LOSTexture.h" #include "graphics/ShaderManager.h" #include "lib/bits.h" #include "lib/config2.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/TimeManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" /* The LOS bitmap is computed with one value per LOS vertex, based on CCmpRangeManager's visibility information. The bitmap is then blurred using an NxN filter (in particular a 7-tap Binomial filter as an efficient integral approximation of a Gaussian). To implement the blur efficiently without using extra memory for a second copy of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side, then the blur shifts the image back into the corner. The blurred bitmap is then uploaded into a GL texture for use by the renderer. */ // Blur with a NxN filter, where N = g_BlurSize must be an odd number. // Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES. static const size_t g_BlurSize = 7; // Alignment (in bytes) of the pixel data passed into texture uploading. // This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since // that's what we set it to) but in some weird cases appears to have a different // value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway. static const size_t g_SubTextureAlignment = 4; CLOSTexture::CLOSTexture(CSimulation2& simulation) : m_Simulation(simulation) { if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) CreateShader(); } CLOSTexture::~CLOSTexture() { m_SmoothFramebuffers[0].reset(); m_SmoothFramebuffers[1].reset(); if (m_Texture) DeleteTexture(); } // Create the LOS texture engine. Should be ran only once. bool CLOSTexture::CreateShader() { m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp); Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader(); m_ShaderInitialized = m_SmoothTech && shader; if (!m_ShaderInitialized) { LOGERROR("Failed to load SmoothLOS shader, disabling."); g_RenderingOptions.SetSmoothLOS(false); return false; } return true; } void CLOSTexture::DeleteTexture() { m_Texture.reset(); m_SmoothTextures[0].reset(); m_SmoothTextures[1].reset(); } void CLOSTexture::MakeDirty() { m_Dirty = true; } Renderer::Backend::ITexture* CLOSTexture::GetTextureSmooth() { if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS()) return GetTexture(); else return m_SmoothTextures[m_WhichTexture].get(); } void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS(); if (!skipSmoothLOS && !m_ShaderInitialized) { if (!CreateShader()) return; // RecomputeTexture will not cause the ConstructTexture to run. // Force the textures to be created. DeleteTexture(); ConstructTexture(deviceCommandContext); m_Dirty = true; } if (m_Dirty) { RecomputeTexture(deviceCommandContext); m_Dirty = false; } if (skipSmoothLOS) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture"); deviceCommandContext->SetFramebuffer(m_SmoothFramebuffers[m_WhichTexture].get()); deviceCommandContext->SetGraphicsPipelineState( m_SmoothTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex1), m_Texture.get()); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex2), m_SmoothTextures[m_WhichTexture].get()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_delta), static_cast(g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f)); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, static_cast(m_Texture->GetWidth()), static_cast(m_Texture->GetHeight()) }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); deviceCommandContext->SetVertexBufferData( 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); deviceCommandContext->SetVertexBufferData( 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); m_WhichTexture = 1u - m_WhichTexture; } Renderer::Backend::ITexture* CLOSTexture::GetTexture() { ENSURE(!m_Dirty); return m_Texture.get(); } const CMatrix3D& CLOSTexture::GetTextureMatrix() { ENSURE(!m_Dirty); return m_TextureMatrix; } const CMatrix3D& CLOSTexture::GetMinimapTextureMatrix() { ENSURE(!m_Dirty); return m_MinimapTextureMatrix; } void CLOSTexture::ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; m_MapSize = cmpRangeManager->GetVerticesPerSide(); const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment)); Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice(); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); + if (backendDevice->IsFramebufferFormatSupported(Renderer::Backend::Format::R8_UNORM)) + { + m_TextureFormat = Renderer::Backend::Format::R8_UNORM; + m_TextureFormatStride = 1; + } + else + { + m_TextureFormat = Renderer::Backend::Format::R8G8B8A8_UNORM; + m_TextureFormatStride = 4; + } + m_Texture = backendDevice->CreateTexture2D("LOSTexture", - Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); + m_TextureFormat, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with SoD color, for the areas we don't // overwrite with uploading later. - std::unique_ptr texData = std::make_unique(textureSize * textureSize); - memset(texData.get(), 0x00, textureSize * textureSize); + const size_t textureDataSize = textureSize * textureSize * m_TextureFormatStride; + std::unique_ptr texData = std::make_unique(textureDataSize); + memset(texData.get(), 0x00, textureDataSize); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) { m_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0", - Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); + m_TextureFormat, textureSize, textureSize, defaultSamplerDesc); m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1", - Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); + m_TextureFormat, textureSize, textureSize, defaultSamplerDesc); - m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer0", - m_SmoothTextures[0].get(), nullptr); - m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer1", - m_SmoothTextures[1].get(), nullptr); + m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer( + "LOSSmoothFramebuffer0", m_SmoothTextures[0].get(), nullptr); + m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer( + "LOSSmoothFramebuffer1", m_SmoothTextures[1].get(), nullptr); if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1]) { LOGERROR("Failed to create LOS framebuffers"); g_RenderingOptions.SetSmoothLOS(false); } deviceCommandContext->UploadTexture( - m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, - texData.get(), textureSize * textureSize); + m_SmoothTextures[0].get(), m_TextureFormat, texData.get(), textureDataSize); deviceCommandContext->UploadTexture( - m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, - texData.get(), textureSize * textureSize); + m_SmoothTextures[1].get(), m_TextureFormat, texData.get(), textureDataSize); } deviceCommandContext->UploadTexture( - m_Texture.get(), Renderer::Backend::Format::A8_UNORM, - texData.get(), textureSize * textureSize); + m_Texture.get(), m_TextureFormat, texData.get(), textureDataSize); texData.reset(); { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. first vertex) // onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel); // world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex) // onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel) float s = (m_MapSize-1) / static_cast(textureSize * (m_MapSize-1) * LOS_TILE_SIZE); float t = 0.5f / textureSize; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)textureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CLOSTexture::RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide()) DeleteTexture(); } bool recreated = false; if (!m_Texture) { ConstructTexture(deviceCommandContext); recreated = true; } PROFILE("recompute LOS texture"); - size_t pitch; - const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch); - ENSURE(pitch * m_MapSize <= dataSize); - std::unique_ptr losData = std::make_unique(dataSize); - CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; + size_t pitch; + const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch); + ENSURE(pitch * m_MapSize <= dataSize); + std::unique_ptr losData = std::make_unique( + dataSize * m_TextureFormatStride); + CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer())); GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch); + // GenerateBitmap writes data tightly packed and we need to offset it to fit + // into the texture format properly. + const size_t textureDataPitch = pitch * m_TextureFormatStride; + if (m_TextureFormatStride > 1) + { + // We skip the last byte because it will be first in our order and we + // don't need to move it. + for (size_t index = 0; index + 1 < dataSize; ++index) + { + const size_t oldAddress = dataSize - 1 - index; + const size_t newAddress = oldAddress * m_TextureFormatStride; + losData[newAddress] = losData[oldAddress]; + losData[oldAddress] = 0; + } + } + if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated) { deviceCommandContext->UploadTextureRegion( - m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, losData.get(), - pitch * m_MapSize, 0, 0, pitch, m_MapSize); + m_SmoothTextures[0].get(), m_TextureFormat, losData.get(), + textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize); deviceCommandContext->UploadTextureRegion( - m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, losData.get(), - pitch * m_MapSize, 0, 0, pitch, m_MapSize); + m_SmoothTextures[1].get(), m_TextureFormat, losData.get(), + textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize); } deviceCommandContext->UploadTextureRegion( - m_Texture.get(), Renderer::Backend::Format::A8_UNORM, losData.get(), - pitch * m_MapSize, 0, 0, pitch, m_MapSize); + m_Texture.get(), m_TextureFormat, losData.get(), + textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize); } size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch) { *pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment); return *pitch * (h + g_BlurSize - 1); } void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch) { u8 *dataPtr = losData; // Initialise the top padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; for (size_t j = 0; j < h; ++j) { // Initialise the left padding for (size_t i = 0; i < g_BlurSize/2; ++i) *dataPtr++ = 0; // Fill in the visibility data for (size_t i = 0; i < w; ++i) { if (los.IsVisible_UncheckedRange(i, j)) *dataPtr++ = 255; else if (los.IsExplored_UncheckedRange(i, j)) *dataPtr++ = 127; else *dataPtr++ = 0; } // Initialise the right padding for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i) *dataPtr++ = 0; } // Initialise the bottom padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; // Horizontal blur: for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0] + 6*d[1] + 15*d[2] + 20*d[3] + 15*d[4] + 6*d[5] + 1*d[6] ) / 64; } } // Vertical blur: for (size_t j = 0; j < h; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0*pitch] + 6*d[1*pitch] + 15*d[2*pitch] + 20*d[3*pitch] + 15*d[4*pitch] + 6*d[5*pitch] + 1*d[6*pitch] ) / 64; } } } Index: ps/trunk/source/graphics/LOSTexture.h =================================================================== --- ps/trunk/source/graphics/LOSTexture.h (revision 26905) +++ ps/trunk/source/graphics/LOSTexture.h (revision 26906) @@ -1,108 +1,115 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_LOSTEXTURE #define INCLUDED_LOSTEXTURE #include "graphics/ShaderTechniquePtr.h" #include "maths/Matrix3D.h" +#include "renderer/backend/Format.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/IFramebuffer.h" #include "renderer/backend/ITexture.h" #include class CLosQuerier; class CSimulation2; /** * Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for * rendering and for the minimap. */ class CLOSTexture { NONCOPYABLE(CLOSTexture); friend class TestLOSTexture; public: CLOSTexture(CSimulation2& simulation); ~CLOSTexture(); /** * Marks the LOS texture as needing recomputation. Call this after each * simulation update, to ensure responsive updates. */ void MakeDirty(); /** * Recomputes the LOS texture if necessary, and returns the texture handle. * Also potentially switches the current active texture unit, and enables texturing on it. * The texture is in 8-bit ALPHA format. */ Renderer::Backend::ITexture* GetTexture(); Renderer::Backend::ITexture* GetTextureSmooth(); void InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture * coordinates, in the form expected by a matrix uniform. * This must only be called after InterpolateLOS. */ const CMatrix3D& GetTextureMatrix(); /** * Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture * coordinates, in the form expected by a matrix uniform. * This must only be called after InterpolateLOS. */ const CMatrix3D& GetMinimapTextureMatrix(); private: void DeleteTexture(); bool CreateShader(); void ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); void RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); size_t GetBitmapSize(size_t w, size_t h, size_t* pitch); void GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch); CSimulation2& m_Simulation; bool m_Dirty = true; bool m_ShaderInitialized = false; + // We need to choose the smallest format. We always use the red channel but + // R8_UNORM might be unavailable on some platforms. So we fallback to + // R8G8B8A8_UNORM. + Renderer::Backend::Format m_TextureFormat = + Renderer::Backend::Format::UNDEFINED; + size_t m_TextureFormatStride = 0; std::unique_ptr m_Texture, m_SmoothTextures[2]; uint32_t m_WhichTexture = 0; // We update textures once a frame, so we change a Framebuffer once a frame. // That allows us to use two ping-pong FBOs instead of checking completeness // of Framebuffer each frame. std::unique_ptr m_SmoothFramebuffers[2]; CShaderTechniquePtr m_SmoothTech; size_t m_MapSize = 0; // vertexes per side CMatrix3D m_TextureMatrix; CMatrix3D m_MinimapTextureMatrix; }; #endif // INCLUDED_LOSTEXTURE Index: ps/trunk/source/graphics/MiniMapTexture.cpp =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26905) +++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26906) @@ -1,725 +1,725 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MiniMapTexture.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniPatch.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgramPtr.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/TextureManager.h" #include "lib/bits.h" #include "lib/timer.h" #include "maths/Vector2D.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/Object.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/system/ParamNode.h" #include namespace { // Set max drawn entities to 64K / 4 for now, which is more than enough. // 4 is the number of vertices per entity. // TODO: we should be cleverer about drawing them to reduce clutter, // f.e. use instancing. const size_t MAX_ENTITIES_DRAWN = 65536 / 4; const size_t MAX_ICON_COUNT = 128; const size_t FINAL_TEXTURE_SIZE = 512; unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = unsigned(float(color & 0xff) * x); unsigned int g = unsigned(float((color >> 8) & 0xff) * x); unsigned int b = unsigned(float((color >> 16) & 0xff) * x); return (0xff000000 | b | g << 8 | r << 16); } void DrawTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const float quadUVs[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f }; const float quadVertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); deviceCommandContext->SetVertexBufferData( 0, quadVertices, std::size(quadVertices) * sizeof(quadVertices[0])); deviceCommandContext->SetVertexBufferData( 1, quadUVs, std::size(quadUVs) * sizeof(quadUVs[0])); deviceCommandContext->Draw(0, 6); } struct MinimapUnitVertex { // This struct is copyable for convenience and because to move is to copy for primitives. u8 r, g, b, a; CVector2D position; }; // Adds a vertex to the passed VertexArray inline void AddEntity(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos, const float entityRadius, const bool useInstancing) { if (useInstancing) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.position.X; (*attrPos)[1] = v.position.Y; ++attrPos; return; } const CVector2D offsets[4] = { {-entityRadius, 0.0f}, {0.0f, -entityRadius}, {entityRadius, 0.0f}, {0.0f, entityRadius} }; for (const CVector2D& offset : offsets) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.position.X + offset.X; (*attrPos)[1] = v.position.Y + offset.Y; ++attrPos; } } } // anonymous namespace CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation) : m_Simulation(simulation), m_IndexArray(false), m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true), m_InstanceVertexArray(Renderer::Backend::IBuffer::Type::VERTEX, false) { // Register Relax NG validator. CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); m_ShallowPassageHeight = GetShallowPassageHeight(); double blinkDuration = 1.0; // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); } m_HalfBlinkDuration = blinkDuration / 2.0; m_AttributePos.format = Renderer::Backend::Format::R32G32_SFLOAT; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4); m_VertexArray.Layout(); m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i) *index++ = 0; m_IndexArray.Upload(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++i) { (*attrColor)[0] = 0; (*attrColor)[1] = 0; (*attrColor)[2] = 0; (*attrColor)[3] = 0; ++attrColor; (*attrPos)[0] = -10000.0f; (*attrPos)[1] = -10000.0f; ++attrPos; } m_VertexArray.Upload(); if (g_VideoMode.GetBackendDevice()->GetCapabilities().instancing) { m_UseInstancing = true; const size_t numberOfCircleSegments = 8; m_InstanceAttributePosition.format = Renderer::Backend::Format::R32G32_SFLOAT; m_InstanceVertexArray.AddAttribute(&m_InstanceAttributePosition); m_InstanceVertexArray.SetNumberOfVertices(numberOfCircleSegments * 3); m_InstanceVertexArray.Layout(); VertexArrayIterator attributePosition = m_InstanceAttributePosition.GetIterator(); for (size_t segment = 0; segment < numberOfCircleSegments; ++segment) { const float currentAngle = static_cast(segment) / numberOfCircleSegments * 2.0f * M_PI; const float nextAngle = static_cast(segment + 1) / numberOfCircleSegments * 2.0f * M_PI; (*attributePosition)[0] = 0.0f; (*attributePosition)[1] = 0.0f; ++attributePosition; (*attributePosition)[0] = std::cos(currentAngle); (*attributePosition)[1] = std::sin(currentAngle); ++attributePosition; (*attributePosition)[0] = std::cos(nextAngle); (*attributePosition)[1] = std::sin(nextAngle); ++attributePosition; } m_InstanceVertexArray.Upload(); m_InstanceVertexArray.FreeBackingStore(); } } CMiniMapTexture::~CMiniMapTexture() { DestroyTextures(); } void CMiniMapTexture::Update(const float UNUSED(deltaRealTime)) { if (m_WaterHeight != g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight) { m_TerrainTextureDirty = true; m_FinalTextureDirty = true; } } void CMiniMapTexture::Render(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain) return; if (!m_TerrainTexture) CreateTextures(deviceCommandContext, terrain); if (m_TerrainTextureDirty) RebuildTerrainTexture(deviceCommandContext, terrain); RenderFinalTexture(deviceCommandContext); } void CMiniMapTexture::CreateTextures( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain) { DestroyTextures(); m_MapSize = terrain->GetVerticesPerSide(); const size_t textureSize = round_up_to_pow2(static_cast(m_MapSize)); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice(); // Create terrain texture m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with solid black, for the areas we don't // overwrite with uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize); for (size_t i = 0; i < textureSize * textureSize; ++i) texData[i] = 0xFF000000; deviceCommandContext->UploadTexture( m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, texData.get(), textureSize * textureSize * 4); texData.reset(); m_TerrainData = std::make_unique((m_MapSize - 1) * (m_MapSize - 1)); m_FinalTexture = backendDevice->CreateTexture2D("MiniMapFinalTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc); m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer("MiniMapFinalFramebuffer", m_FinalTexture.get(), nullptr); ENSURE(m_FinalTextureFramebuffer); } void CMiniMapTexture::DestroyTextures() { m_TerrainTexture.reset(); m_FinalTexture.reset(); m_TerrainData.reset(); } void CMiniMapTexture::RebuildTerrainTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain) { const u32 x = 0; const u32 y = 0; const u32 width = m_MapSize - 1; const u32 height = m_MapSize - 1; m_WaterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight; m_TerrainTextureDirty = false; for (u32 j = 0; j < height; ++j) { u32* dataPtr = m_TerrainData.get() + ((y + j) * width) + x; for (u32 i = 0; i < width; ++i) { const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j) + terrain->GetVertexGroundLevel((int)i+1, (int)j) + terrain->GetVertexGroundLevel((int)i, (int)j+1) + terrain->GetVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) { // shallow water *dataPtr++ = 0xffc09870; } else if (avgHeight < m_WaterHeight) { // Set water as constant color for consistency on different maps *dataPtr++ = 0xffa07850; } else { int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0xFFFFFFFF; CMiniPatch* mp = terrain->GetTile(x + i, y + j); if (mp) { CTerrainTextureEntry* tex = mp->GetTextureEntry(); if (tex) { // If the texture can't be loaded yet, set the dirty flags // so we'll try regenerating the terrain texture again soon if (!tex->GetTexture()->TryLoad()) m_TerrainTextureDirty = true; color = tex->GetBaseColor(); } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture deviceCommandContext->UploadTextureRegion( m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, m_TerrainData.get(), width * height * 4, 0, 0, width, height); } void CMiniMapTexture::RenderFinalTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation const double currentTime = timer_Time(); const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty; if (doUpdate) m_LastFinalTextureUpdate = currentTime; else return; m_FinalTextureDirty = false; GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture"); deviceCommandContext->SetFramebuffer(m_FinalTextureFramebuffer.get()); const SViewPort oldViewPort = g_Renderer.GetViewport(); const SViewPort viewPort = { 0, 0, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE }; g_Renderer.SetViewport(viewPort); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); ENSURE(cmpRangeManager); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize); const float texCoordMax = m_TerrainTexture ? static_cast(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f; Renderer::Backend::IShaderProgram* shader = nullptr; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); shader = tech->GetShader(); if (m_TerrainTexture) { deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), m_TerrainTexture.get()); } CMatrix3D baseTransform; baseTransform.SetIdentity(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); CMatrix3D terrainTransform; terrainTransform.SetIdentity(); terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), terrainTransform.AsFloatArray()); if (m_TerrainTexture) DrawTexture(deviceCommandContext); deviceCommandContext->EndPass(); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.blendState.colorWriteMask = Renderer::Backend::ColorWriteMask::RED | Renderer::Backend::ColorWriteMask::GREEN | Renderer::Backend::ColorWriteMask::BLUE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); // Draw territory boundaries CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), territoryTexture.GetTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), territoryTexture.GetMinimapTextureMatrix().AsFloatArray()); DrawTexture(deviceCommandContext); deviceCommandContext->EndPass(); - pipelineStateDesc.blendState.enabled = false; - pipelineStateDesc.blendState.colorWriteMask = - Renderer::Backend::ColorWriteMask::ALPHA; - deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); + tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap_los, CShaderDefines()); + deviceCommandContext->SetGraphicsPipelineState( + tech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); + shader = tech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), losTexture.GetTexture()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), losTexture.GetMinimapTextureMatrix().AsFloatArray()); DrawTexture(deviceCommandContext); deviceCommandContext->EndPass(); // We might scale entities properly in the vertex shader but it requires // additional space in the vertex buffer. So we assume that we don't need // to change an entity size so often. // Radius with instancing is lower because an entity has a more round shape. const float entityRadius = static_cast(m_MapSize) / 128.0f * (m_UseInstancing ? 5.0 : 6.0f); if (doUpdate) { m_Icons.clear(); CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); if (currentTime > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = currentTime + m_HalfBlinkDuration; } bool iconsEnabled = false; CFG_GET_VAL("gui.session.minimap.icons.enabled", iconsEnabled); float iconsOpacity = 1.0f; CFG_GET_VAL("gui.session.minimap.icons.opacity", iconsOpacity); float iconsSizeScale = 1.0f; CFG_GET_VAL("gui.session.minimap.icons.sizescale", iconsSizeScale); bool iconsCountOverflow = false; entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer()); if (vis != LosVisibility::HIDDEN) { v.a = 255; v.position.X = posX.ToFloat(); v.position.Y = posZ.ToFloat(); // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { AddEntity(v, attrColor, attrPos, entityRadius, m_UseInstancing); ++m_EntitiesDrawn; } if (!iconsEnabled || !cmpMinimap->HasIcon()) continue; if (m_Icons.size() < MAX_ICON_COUNT) { CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture( CTextureProperties(cmpMinimap->GetIconPath())); const CColor color(v.r / 255.0f, v.g / 255.0f, v.b / 255.0f, iconsOpacity); m_Icons.emplace_back(Icon{ std::move(texture), color, v.position, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f}); } else { iconsCountOverflow = true; } } } } if (iconsCountOverflow) LOGWARNING("Too many minimap icons to draw: %zu/%zu", m_Icons.size(), MAX_ICON_COUNT); // Add the pinged vertices at the end, so they are drawn on top for (const MinimapUnitVertex& vertex : pingingVertices) { AddEntity(vertex, attrColor, attrPos, entityRadius, m_UseInstancing); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); if (!m_UseInstancing) { VertexArrayIterator index = m_IndexArray.GetIterator(); for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex) { index[entityIndex * 6 + 0] = static_cast(entityIndex * 4 + 0); index[entityIndex * 6 + 1] = static_cast(entityIndex * 4 + 1); index[entityIndex * 6 + 2] = static_cast(entityIndex * 4 + 2); index[entityIndex * 6 + 3] = static_cast(entityIndex * 4 + 0); index[entityIndex * 6 + 4] = static_cast(entityIndex * 4 + 2); index[entityIndex * 6 + 5] = static_cast(entityIndex * 4 + 3); } m_IndexArray.Upload(); } m_VertexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); if (m_UseInstancing) pointDefines.Add(str_USE_GPU_INSTANCING, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); shader = tech->GetShader(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); CMatrix3D unitMatrix; unitMatrix.SetIdentity(); // Convert world space coordinates into [0, 2]. const float unitScale = invTileMapSize; unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f); // Offset the coordinates to [-1, 1]. unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f)); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), unitMatrix.AsFloatArray()); Renderer::Backend::IDeviceCommandContext::Rect scissorRect; scissorRect.x = scissorRect.y = 1; scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2; deviceCommandContext->SetScissors(1, &scissorRect); m_VertexArray.UploadIfNeeded(deviceCommandContext); const uint32_t stride = m_VertexArray.GetStride(); const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride; if (m_UseInstancing) { deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, m_AttributePos.format, m_InstanceVertexArray.GetOffset() + m_InstanceAttributePosition.offset, m_InstanceVertexArray.GetStride(), Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV1, m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::COLOR, m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1); deviceCommandContext->SetVertexBuffer(0, m_InstanceVertexArray.GetBuffer()); deviceCommandContext->SetVertexBuffer(1, m_VertexArray.GetBuffer()); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), entityRadius); deviceCommandContext->DrawInstanced(0, m_InstanceVertexArray.GetNumberOfVertices(), 0, m_EntitiesDrawn); } else { m_IndexArray.UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, m_AttributePos.format, firstVertexOffset + m_AttributePos.offset, stride, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::COLOR, m_AttributeColor.format, firstVertexOffset + m_AttributeColor.offset, stride, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexBuffer(0, m_VertexArray.GetBuffer()); deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer()); deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0); } g_Renderer.GetStats().m_DrawCalls++; deviceCommandContext->SetScissors(0, nullptr); deviceCommandContext->EndPass(); } deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); g_Renderer.SetViewport(oldViewPort); } // static float CMiniMapTexture::GetShallowPassageHeight() { float shallowPassageHeight = 0.0f; CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); return shallowPassageHeight; } Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 26905) +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 26906) @@ -1,498 +1,497 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CMiniMap.h" #include "graphics/Canvas2D.h" #include "graphics/GameView.h" -#include "graphics/LOSTexture.h" #include "graphics/MiniMapTexture.h" #include "graphics/MiniPatch.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgramPtr.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TextureManager.h" #include "gui/CGUI.h" #include "gui/GUIManager.h" #include "gui/GUIMatrix.h" #include "lib/bits.h" #include "lib/external_libraries/libsdl.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/Object.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" #include "simulation2/system/ParamNode.h" #include #include #include namespace { // Adds segments pieces lying inside the circle to lines. void CropPointsByCircle(const std::array& points, const CVector3D& center, const float radius, std::vector* lines) { constexpr float EPS = 1e-3f; lines->reserve(points.size() * 2); for (size_t idx = 0; idx < points.size(); ++idx) { const CVector3D& currentPoint = points[idx]; const CVector3D& nextPoint = points[(idx + 1) % points.size()]; const CVector3D direction = (nextPoint - currentPoint).Normalized(); const CVector3D normal(direction.Z, 0.0f, -direction.X); const float offset = normal.Dot(currentPoint) - normal.Dot(center); // We need to have lines only inside the circle. if (std::abs(offset) + EPS >= radius) continue; const CVector3D closestPoint = center + normal * offset; const float halfChordLength = sqrt(radius * radius - offset * offset); const CVector3D intersectionA = closestPoint - direction * halfChordLength; const CVector3D intersectionB = closestPoint + direction * halfChordLength; // We have no intersection if the segment is lying outside of the circle. if (direction.Dot(currentPoint) + EPS > direction.Dot(intersectionB) || direction.Dot(nextPoint) - EPS < direction.Dot(intersectionA)) continue; lines->emplace_back( direction.Dot(currentPoint) > direction.Dot(intersectionA) ? currentPoint : intersectionA); lines->emplace_back( direction.Dot(nextPoint) < direction.Dot(intersectionB) ? nextPoint : intersectionB); } } void DrawTexture( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, float angle, float x, float y, float x2, float y2, float mapScale) { // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) // Scale square maps to fit in circular minimap area const float s = sin(angle) * mapScale; const float c = cos(angle) * mapScale; const float m = 0.5f; float quadTex[] = { m*(-c + s + 1.f), m*(-c + -s + 1.f), m*(c + s + 1.f), m*(-c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(-c + -s + 1.f), m*(c + -s + 1.f), m*(-c + s + 1.f), m*(-c + -s + 1.f) }; float quadVerts[] = { x, y, 0.0f, x2, y, 0.0f, x2, y2, 0.0f, x2, y2, 0.0f, x, y2, 0.0f, x, y, 0.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); deviceCommandContext->SetVertexBufferData( 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); deviceCommandContext->SetVertexBufferData( 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); } } // anonymous namespace const CStr CMiniMap::EventNameWorldClick = "WorldClick"; CMiniMap::CMiniMap(CGUI& pGUI) : IGUIObject(pGUI), m_MapSize(0), m_MapScale(1.f), m_Mask(this, "mask", false), m_FlareTextureCount(this, "flare_texture_count", 0), m_FlareRenderSize(this, "flare_render_size", 0), m_FlareInterleave(this, "flare_interleave", false), m_FlareAnimationSpeed(this, "flare_animation_speed", 0.0f), m_FlareLifetimeSeconds(this, "flare_lifetime_seconds", 0.0f), m_FlareStartFadeSeconds(this, "flare_start_fade_seconds", 0.0f), m_FlareStopFadeSeconds(this, "flare_stop_fade_seconds", 0.0f) { m_Clicking = false; m_MouseHovering = false; } CMiniMap::~CMiniMap() = default; void CMiniMap::HandleMessage(SGUIMessage& Message) { IGUIObject::HandleMessage(Message); switch (Message.type) { case GUIM_LOAD: RecreateFlareTextures(); break; case GUIM_SETTINGS_UPDATED: if (Message.value == "flare_texture_count") RecreateFlareTextures(); break; case GUIM_MOUSE_PRESS_LEFT: if (m_MouseHovering) { if (!CMiniMap::FireWorldClickEvent(SDL_BUTTON_LEFT, 1)) { SetCameraPositionFromMousePosition(); m_Clicking = true; } } break; case GUIM_MOUSE_RELEASE_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPositionFromMousePosition(); m_Clicking = false; break; case GUIM_MOUSE_DBLCLICK_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPositionFromMousePosition(); m_Clicking = false; break; case GUIM_MOUSE_ENTER: m_MouseHovering = true; break; case GUIM_MOUSE_LEAVE: m_Clicking = false; m_MouseHovering = false; break; case GUIM_MOUSE_RELEASE_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); break; case GUIM_MOUSE_DBLCLICK_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); break; case GUIM_MOUSE_MOTION: if (m_MouseHovering && m_Clicking) SetCameraPositionFromMousePosition(); break; case GUIM_MOUSE_WHEEL_DOWN: case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; default: break; } } void CMiniMap::RecreateFlareTextures() { // Catch invalid values. if (m_FlareTextureCount > 99) { LOGERROR("Invalid value for flare texture count. Valid range is 0-99."); return; } const CStr textureNumberingFormat = "art/textures/animated/minimap-flare/frame%02u.png"; m_FlareTextures.clear(); m_FlareTextures.reserve(m_FlareTextureCount); for (u32 i = 0; i < m_FlareTextureCount; ++i) { CTextureProperties textureProps(fmt::sprintf(textureNumberingFormat, i).c_str()); textureProps.SetIgnoreQuality(true); m_FlareTextures.emplace_back(g_Renderer.GetTextureManager().CreateTexture(textureProps)); } } bool CMiniMap::IsMouseOver() const { const CVector2D& mousePos = m_pGUI.GetMousePos(); // Take the magnitude of the difference of the mouse position and minimap center. const float distanceFromCenter = (mousePos - m_CachedActualSize.CenterPoint()).Length(); // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. return distanceFromCenter < m_CachedActualSize.GetWidth() / 2.0; } void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const { // Determine X and Z according to proportion of mouse position and minimap. const CVector2D& mousePos = m_pGUI.GetMousePos(); float px = (mousePos.X - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); float py = (m_CachedActualSize.bottom - mousePos.Y) / m_CachedActualSize.GetHeight(); float angle = GetAngle(); // Scale world coordinates for shrunken square map x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); } void CMiniMap::SetCameraPositionFromMousePosition() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); CVector3D target; GetMouseWorldCoordinates(target.X, target.Z); target.Y = terrain->GetExactGroundLevel(target.X, target.Z); g_Game->GetView()->MoveCameraTarget(target); } float CMiniMap::GetAngle() const { CVector3D cameraIn = g_Game->GetView()->GetCamera()->GetOrientation().GetIn(); return -atan2(cameraIn.X, cameraIn.Z); } CVector2D CMiniMap::WorldSpaceToMiniMapSpace(const CVector3D& worldPosition) const { // Coordinates with 0,0 in the middle of the minimap and +-0.5 as max. const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize); const float relativeX = (worldPosition.X * invTileMapSize - 0.5) / m_MapScale; const float relativeY = (worldPosition.Z * invTileMapSize - 0.5) / m_MapScale; // Rotate coordinates. const float angle = GetAngle(); const float rotatedX = cos(angle) * relativeX + sin(angle) * relativeY; const float rotatedY = -sin(angle) * relativeX + cos(angle) * relativeY; // Calculate coordinates in GUI space. return CVector2D( m_CachedActualSize.left + (0.5f + rotatedX) * m_CachedActualSize.GetWidth(), m_CachedActualSize.bottom - (0.5f + rotatedY) * m_CachedActualSize.GetHeight()); } bool CMiniMap::FireWorldClickEvent(int button, int UNUSED(clicks)) { ScriptRequest rq(g_GUI->GetActiveGUI()->GetScriptInterface()); float x, z; GetMouseWorldCoordinates(x, z); JS::RootedValue coords(rq.cx); Script::CreateObject(rq, &coords, "x", x, "z", z); JS::RootedValue buttonJs(rq.cx); Script::ToJSVal(rq, &buttonJs, button); JS::RootedValueVector paramData(rq.cx); ignore_result(paramData.append(coords)); ignore_result(paramData.append(buttonJs)); return ScriptEventWithReturn(EventNameWorldClick, paramData); } // This sets up and draws the rectangle on the minimap // which represents the view of the camera in the world. void CMiniMap::DrawViewRect(CCanvas2D& canvas) const { // Compute the camera frustum intersected with a fixed-height plane. // Use the water height as a fixed base height, which should be the lowest we can go const float sampleHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight; const CCamera* camera = g_Game->GetView()->GetCamera(); const std::array hitPoints = { camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), sampleHeight), camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), sampleHeight), camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, sampleHeight), camera->GetWorldCoordinates(0, 0, sampleHeight) }; std::vector worldSpaceLines; // We need to prevent drawing view bounds out of the map. const float halfMapSize = static_cast((m_MapSize - 1) * TERRAIN_TILE_SIZE) * 0.5f; CropPointsByCircle(hitPoints, CVector3D(halfMapSize, 0.0f, halfMapSize), halfMapSize * m_MapScale, &worldSpaceLines); if (worldSpaceLines.empty()) return; for (size_t index = 0; index < worldSpaceLines.size() && index + 1 < worldSpaceLines.size(); index += 2) { const CVector2D from = WorldSpaceToMiniMapSpace(worldSpaceLines[index]); const CVector2D to = WorldSpaceToMiniMapSpace(worldSpaceLines[index + 1]); canvas.DrawLine({from, to}, 2.0f, CColor(1.0f, 0.3f, 0.3f, 1.0f)); } } void CMiniMap::DrawFlare(CCanvas2D& canvas, const MapFlare& flare, double currentTime) const { if (m_FlareTextures.empty()) return; const CVector2D flareCenter = WorldSpaceToMiniMapSpace(CVector3D(flare.pos.X, 0.0f, flare.pos.Y)); const CRect destination( flareCenter.X - m_FlareRenderSize, flareCenter.Y - m_FlareRenderSize, flareCenter.X + m_FlareRenderSize, flareCenter.Y + m_FlareRenderSize); const double deltaTime = currentTime - flare.time; const double remainingTime = m_FlareLifetimeSeconds - deltaTime; const u32 flooredStep = floor(deltaTime * m_FlareAnimationSpeed); const float startFadeAlpha = m_FlareStartFadeSeconds > 0.0f ? deltaTime / m_FlareStartFadeSeconds : 1.0f; const float stopFadeAlpha = m_FlareStopFadeSeconds > 0.0f ? remainingTime / m_FlareStopFadeSeconds : 1.0f; const float alpha = Clamp(std::min( SmoothStep(0.0f, 1.0f, startFadeAlpha), SmoothStep(0.0f, 1.0f, stopFadeAlpha)), 0.0f, 1.0f); DrawFlareFrame(canvas, flooredStep % m_FlareTextures.size(), destination, flare.color, alpha); // Draw a second circle if the first has reached half of the animation. if (m_FlareInterleave && flooredStep >= m_FlareTextures.size() / 2) { DrawFlareFrame(canvas, (flooredStep - m_FlareTextures.size() / 2) % m_FlareTextures.size(), destination, flare.color, alpha); } } void CMiniMap::DrawFlareFrame(CCanvas2D& canvas, const u32 frameIndex, const CRect& destination, const CColor& color, float alpha) const { // TODO: Only draw inside the minimap circle. CTexturePtr texture = m_FlareTextures[frameIndex % m_FlareTextures.size()]; CColor finalColor = color; finalColor.a *= alpha; canvas.DrawTexture(texture, destination, CRect(0, 0, texture->GetWidth(), texture->GetHeight()), finalColor, CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f); } void CMiniMap::Draw(CCanvas2D& canvas) { PROFILE3("render minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if (!g_Game || !g_Game->IsGameStarted()) return; if (!m_Mask) canvas.DrawRect(m_CachedActualSize, CColor(0.0f, 0.0f, 0.0f, 1.0f)); canvas.Flush(); CSimulation2* sim = g_Game->GetSimulation2(); CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); ENSURE(cmpRangeManager); // Set our globals in case they hadn't been set before const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); m_MapSize = terrain->GetVerticesPerSide(); m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); // Draw the main textured quad CMiniMapTexture& miniMapTexture = g_Game->GetView()->GetMiniMapTexture(); if (miniMapTexture.GetTexture()) { CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; Renderer::Backend::IDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = tech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_baseTex), miniMapTexture.GetTexture()); const CMatrix3D baseTransform = GetDefaultGuiMatrix(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_transform), baseTransform.AsFloatArray()); deviceCommandContext->SetUniform( shader->GetBindingSlot(str_textureTransform), baseTextureTransform.AsFloatArray()); const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float angle = GetAngle(); DrawTexture(deviceCommandContext, angle, x, y, x2, y2, m_MapScale); deviceCommandContext->EndPass(); } for (const CMiniMapTexture::Icon& icon : miniMapTexture.GetIcons()) { const CVector2D center = WorldSpaceToMiniMapSpace( CVector3D(icon.worldPosition.X, 0.0f, icon.worldPosition.Y)); const CRect destination( center.X - icon.halfSize, center.Y - icon.halfSize, center.X + icon.halfSize, center.Y + icon.halfSize); const CRect source(0, 0, icon.texture->GetWidth(), icon.texture->GetHeight()); canvas.DrawTexture( icon.texture, destination, source, icon.color, CColor(0.0f, 0.0f, 0.0f, 0.0f), 0.0f); } PROFILE_START("minimap flares"); DrawViewRect(canvas); const double currentTime = timer_Time(); while (!m_MapFlares.empty() && m_FlareLifetimeSeconds + m_MapFlares.front().time < currentTime) m_MapFlares.pop_front(); for (const MapFlare& flare : m_MapFlares) DrawFlare(canvas, flare, currentTime); PROFILE_END("minimap flares"); } bool CMiniMap::Flare(const CVector2D& pos, const CStr& colorStr) { CColor color; if (!color.ParseString(colorStr)) { LOGERROR("CMiniMap::Flare: Couldn't parse color string"); return false; } m_MapFlares.push_back({ pos, color, timer_Time() }); return true; } Index: ps/trunk/source/ps/CStrInternStatic.h =================================================================== --- ps/trunk/source/ps/CStrInternStatic.h (revision 26905) +++ ps/trunk/source/ps/CStrInternStatic.h (revision 26906) @@ -1,193 +1,194 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ // This file defines global CStrIntern variables, to avoid the cost of // constructing CStrInterns frequently at runtime. // // A line like // X(foo) // defines a variable str_foo with value "foo". // // A line like // X2(foo_0, "foo[0]") // defines a variable str_foo_0 with value "foo[0]". // For direct inclusion, we presumably just want the extern definitions. #ifndef X #include "CStrIntern.h" #define X(id) extern CStrIntern str_##id; #define X2(id, str) extern CStrIntern str_##id; #endif X(0) X(1) X(2) X(3) X(4) X(ALPHABLEND_PASS_BLEND) X(ALPHABLEND_PASS_OPAQUE) X(BLEND) X(BLOOM_NOP) X(BLOOM_PASS_H) X(BLOOM_PASS_V) X(DECAL) X(DISABLE_RECEIVE_SHADOWS) X(IGNORE_LOS) X(MINIMAP_BASE) X(MINIMAP_LINE) X(MINIMAP_LOS) X(MINIMAP_MASK) X(MINIMAP_POINT) X(MODE_SHADOWCAST) X(MODE_SILHOUETTEDISPLAY) X(MODE_SILHOUETTEOCCLUDER) X(MODE_WIREFRAME) X(MODE_WIREFRAME_SOLID) X(PASS_REFLECTIONS) X(PASS_REFRACTIONS) X(PASS_SHADOWS) X(RENDER_DEBUG_MODE) X(RENDER_DEBUG_MODE_AO) X(RENDER_DEBUG_MODE_ALPHA) X(RENDER_DEBUG_MODE_CUSTOM) X(RENDER_DEBUG_MODE_NONE) X(SHADOWS_CASCADE_COUNT) X(USE_FANCY_EFFECTS) X(USE_FP_SHADOW) X(USE_GPU_INSTANCING) X(USE_GPU_SKINNING) X(USE_INSTANCING) X(USE_NORMALS) X(USE_OBJECTCOLOR) X(USE_REAL_DEPTH) X(USE_REFLECTION) X(USE_REFRACTION) X(USE_SHADOW) X(USE_SHADOW_PCF) X(USE_SHADOW_SAMPLER) X(USE_FOG) X(WATERTYPE_CLAP) X(WATERTYPE_LAKE) X2(_emptystring, "") X(a_apexPosition) X(a_otherPosition) X(a_retreatPosition) X(a_skinJoints) X(a_skinWeights) X(a_splashPosition) X(a_tangent) X(a_waterInfo) X(ambient) X(baseTex) X(blendTex) X(bloom) X(blurTex2) X(blurTex4) X(blurTex8) X(brightness) X(cameraForward) X(cameraPos) X(canvas2d) X(color) X(colorAdd) X(colorMul) X(debug_line) X(debug_overlay) X(delta) X(depthTex) X(dummy) X(foamTex) X(fogColor) X(fogParams) X(foreground_overlay) X(grayscaleFactor) X(hdr) X(height) X(instancingTransform) X(losTex) X(losTex1) X(losTex2) X(losTransform) X(los_interp) X(mapSize) X(maskTex) X(maskTextureTransform) X(minimap) +X(minimap_los) X(modelViewMatrix) X(murkiness) X(normalMap) X(normalMap2) X(objectColor) X(overlay_line) X(overlay_solid) X(particle_add) X(particle_multiply) X(particle_overlay) X(particle_solid) X(particle_subtract) X(playerColor) X(projInvTransform) X(qualityLevel) X(reflectionMap) X(reflectionMatrix) X(refractionMap) X(refractionMatrix) X(renderedTex) X(repeatScale) X2(sans_10, "sans-10"); X(saturation) X(screenSize) X(shadingColor) X(shadowDistance) X(shadowDistances) X(shadowScale) X(shadowTex) X(shadowTransform) X(shadowTransforms) X(sharpness) X(skinBlendMatrices) X(skyBoxRot) X(skyCube) X(sky_simple) X(solid) X(sunColor) X(sunDir) X(terrain_solid) X(tex) X(texSize) X(textureTransform) X(time) X(tint) X(transform) X(translation) X(viewInvTransform) X(water_high) X(water_simple) X(water_waves) X(waterEffectsTex) X(waterTex) X(waveTex) X(waviness) X(waveParams1) X(waveParams2) X(width) X(windAngle) X(zFar) X(zNear) #undef X #undef X2 Index: ps/trunk/source/renderer/backend/Format.h =================================================================== --- ps/trunk/source/renderer/backend/Format.h (revision 26905) +++ ps/trunk/source/renderer/backend/Format.h (revision 26906) @@ -1,66 +1,69 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_FORMAT #define INCLUDED_RENDERER_BACKEND_FORMAT namespace Renderer { namespace Backend { enum class Format { UNDEFINED, + R8_UNORM, R8G8_UNORM, R8G8_UINT, R8G8B8_UNORM, R8G8B8A8_UNORM, R8G8B8A8_UINT, + // TODO: we need to drop legacy A8 and L8 formats as soon as we have proper + // channel swizzling. A8_UNORM, L8_UNORM, R16_UNORM, R16_UINT, R16_SINT, R16G16_UNORM, R16G16_UINT, R16G16_SINT, R32_SFLOAT, R32G32_SFLOAT, R32G32B32_SFLOAT, R32G32B32A32_SFLOAT, D16, D24, D24_S8, D32, BC1_RGB_UNORM, BC1_RGBA_UNORM, BC2_UNORM, BC3_UNORM }; } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_FORMAT Index: ps/trunk/source/renderer/backend/IDevice.h =================================================================== --- ps/trunk/source/renderer/backend/IDevice.h (revision 26905) +++ ps/trunk/source/renderer/backend/IDevice.h (revision 26906) @@ -1,109 +1,111 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_IDEVICE #define INCLUDED_RENDERER_BACKEND_IDEVICE #include "graphics/Color.h" #include "renderer/backend/Format.h" #include "renderer/backend/IBuffer.h" #include "renderer/backend/IDevice.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/IFramebuffer.h" #include "renderer/backend/IShaderProgram.h" #include "renderer/backend/ITexture.h" #include "scriptinterface/ScriptForward.h" #include #include #include class CShaderDefines; class CStr; namespace Renderer { namespace Backend { class IDevice { public: struct Capabilities { bool S3TC; bool ARBShaders; bool ARBShadersShadow; bool computeShaders; bool debugLabels; bool debugScopedLabels; bool multisampling; bool anisotropicFiltering; uint32_t maxSampleCount; float maxAnisotropy; uint32_t maxTextureSize; bool instancing; }; virtual ~IDevice() {} virtual const std::string& GetName() const = 0; virtual const std::string& GetVersion() const = 0; virtual const std::string& GetDriverInformation() const = 0; virtual const std::vector& GetExtensions() const = 0; virtual void Report(const ScriptRequest& rq, JS::HandleValue settings) = 0; virtual IFramebuffer* GetCurrentBackbuffer() = 0; virtual std::unique_ptr CreateCommandContext() = 0; virtual std::unique_ptr CreateTexture(const char* name, const ITexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) = 0; virtual std::unique_ptr CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) = 0; virtual std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) = 0; virtual std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) = 0; virtual std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) = 0; virtual std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) = 0; virtual void Present() = 0; virtual bool IsTextureFormatSupported(const Format format) const = 0; + virtual bool IsFramebufferFormatSupported(const Format format) const = 0; + virtual const Capabilities& GetCapabilities() const = 0; }; } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_IDEVICE Index: ps/trunk/source/renderer/backend/dummy/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 26906) @@ -1,129 +1,134 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "renderer/backend/dummy/Buffer.h" #include "renderer/backend/dummy/DeviceCommandContext.h" #include "renderer/backend/dummy/Framebuffer.h" #include "renderer/backend/dummy/ShaderProgram.h" #include "renderer/backend/dummy/Texture.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" namespace Renderer { namespace Backend { namespace Dummy { CDevice::CDevice() { m_Name = "Dummy"; m_Version = "Unknown"; m_DriverInformation = "Unknown"; m_Extensions = {}; m_Backbuffer = CFramebuffer::Create(this); m_Capabilities.S3TC = true; m_Capabilities.ARBShaders = false; m_Capabilities.ARBShadersShadow = false; m_Capabilities.computeShaders = true; m_Capabilities.debugLabels = true; m_Capabilities.debugScopedLabels = true; m_Capabilities.multisampling = true; m_Capabilities.anisotropicFiltering = true; m_Capabilities.maxSampleCount = 4u; m_Capabilities.maxAnisotropy = 16.0f; m_Capabilities.maxTextureSize = 8192u; m_Capabilities.instancing = true; } CDevice::~CDevice() = default; void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { Script::SetProperty(rq, settings, "name", "dummy"); } std::unique_ptr CDevice::CreateCommandContext() { return CDeviceCommandContext::Create(this); } std::unique_ptr CDevice::CreateTexture(const char* UNUSED(name), const CTexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& UNUSED(defaultSamplerDesc), const uint32_t MIPLevelCount, const uint32_t UNUSED(sampleCount)) { return CTexture::Create(this, type, format, width, height, MIPLevelCount); } std::unique_ptr CDevice::CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture(name, ITexture::Type::TEXTURE_2D, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char*, ITexture*, ITexture*) { return CFramebuffer::Create(this); } std::unique_ptr CDevice::CreateFramebuffer( const char*, ITexture*, ITexture*, const CColor&) { return CFramebuffer::Create(this); } std::unique_ptr CDevice::CreateBuffer( const char*, const CBuffer::Type type, const uint32_t size, const bool dynamic) { return CBuffer::Create(this, type, size, dynamic); } std::unique_ptr CDevice::CreateShaderProgram( const CStr&, const CShaderDefines&) { return CShaderProgram::Create(this); } void CDevice::Present() { // We have nothing to present. } bool CDevice::IsTextureFormatSupported(const Format UNUSED(format)) const { return true; } +bool CDevice::IsFramebufferFormatSupported(const Format UNUSED(format)) const +{ + return true; +} + } // namespace Dummy } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/dummy/Device.h =================================================================== --- ps/trunk/source/renderer/backend/dummy/Device.h (revision 26905) +++ ps/trunk/source/renderer/backend/dummy/Device.h (revision 26906) @@ -1,99 +1,101 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE #define INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE #include "renderer/backend/IDevice.h" class CShaderDefines; namespace Renderer { namespace Backend { namespace Dummy { class CDeviceCommandContext; class CDevice : public IDevice { public: CDevice(); ~CDevice() override; const std::string& GetName() const override { return m_Name; } const std::string& GetVersion() const override { return m_Version; } const std::string& GetDriverInformation() const override { return m_DriverInformation; } const std::vector& GetExtensions() const override { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings) override; IFramebuffer* GetCurrentBackbuffer() override { return m_Backbuffer.get(); } std::unique_ptr CreateCommandContext() override; std::unique_ptr CreateTexture(const char* name, const ITexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) override; std::unique_ptr CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) override; std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; void Present() override; bool IsTextureFormatSupported(const Format format) const override; + bool IsFramebufferFormatSupported(const Format format) const override; + const Capabilities& GetCapabilities() const override { return m_Capabilities; } protected: std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; std::unique_ptr m_Backbuffer; Capabilities m_Capabilities{}; }; } // namespace Dummy } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE Index: ps/trunk/source/renderer/backend/gl/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26906) @@ -1,919 +1,940 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Profile.h" #include "renderer/backend/gl/DeviceCommandContext.h" #include "renderer/backend/gl/Texture.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" #if OS_WIN #include "lib/sysdep/os/win/wgfx.h" // We can't include wutil directly because GL headers conflict with Windows // until we use a proper GL loader. extern void* wutil_GetAppHDC(); #endif #include #include #include // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES #include #include #endif namespace Renderer { namespace Backend { namespace GL { namespace { std::string GetNameImpl() { // GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards. // On top of that WMI can cause crashes with Nvidia Optimus and some netbooks // see http://trac.wildfiregames.com/ticket/1952 // http://trac.wildfiregames.com/ticket/1575 char cardName[128]; const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); // Happens if called before GL initialization. if (!vendor || !renderer) return {}; sprintf_s(cardName, std::size(cardName), "%s %s", vendor, renderer); // Remove crap from vendor names. (don't dare touch the model name - // it's too risky, there are too many different strings). #define SHORTEN(what, charsToKeep) \ if (!strncmp(cardName, what, std::size(what) - 1)) \ memmove(cardName + charsToKeep, cardName + std::size(what) - 1, (strlen(cardName) - (std::size(what) - 1) + 1) * sizeof(char)); SHORTEN("ATI Technologies Inc.", 3); SHORTEN("NVIDIA Corporation", 6); SHORTEN("S3 Graphics", 2); // returned by EnumDisplayDevices SHORTEN("S3 Graphics, Incorporated", 2); // returned by GL_VENDOR #undef SHORTEN return cardName; } std::string GetVersionImpl() { return reinterpret_cast(glGetString(GL_VERSION)); } std::string GetDriverInformationImpl() { const std::string version = GetVersionImpl(); std::string driverInfo; #if OS_WIN driverInfo = CStrW(wgfx_DriverInfo()).ToUTF8(); if (driverInfo.empty()) #endif { if (!version.empty()) { // Add "OpenGL" to differentiate this from the real driver version // (returned by platform-specific detect routines). driverInfo = std::string("OpenGL ") + version; } } if (driverInfo.empty()) return version; return version + " " + driverInfo; } std::vector GetExtensionsImpl() { std::vector extensions; const std::string exts = ogl_ExtensionString(); boost::split(extensions, exts, boost::algorithm::is_space(), boost::token_compress_on); std::sort(extensions.begin(), extensions.end()); return extensions; } void GLAD_API_PTR OnDebugMessage( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei UNUSED(length), const GLchar* message, const void* UNUSED(user_param)) { std::string debugSource = "unknown"; std::string debugType = "unknown"; std::string debugSeverity = "unknown"; switch (source) { case GL_DEBUG_SOURCE_API: debugSource = "the API"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: debugSource = "the window system"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER: debugSource = "the shader compiler"; break; case GL_DEBUG_SOURCE_THIRD_PARTY: debugSource = "a third party"; break; case GL_DEBUG_SOURCE_APPLICATION: debugSource = "the application"; break; case GL_DEBUG_SOURCE_OTHER: debugSource = "somewhere"; break; } switch (type) { case GL_DEBUG_TYPE_ERROR: debugType = "error"; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: debugType = "deprecated behaviour"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: debugType = "undefined behaviour"; break; case GL_DEBUG_TYPE_PORTABILITY: debugType = "portability"; break; case GL_DEBUG_TYPE_PERFORMANCE: debugType = "performance"; break; case GL_DEBUG_TYPE_OTHER: debugType = "other"; break; case GL_DEBUG_TYPE_MARKER: debugType = "marker"; break; case GL_DEBUG_TYPE_PUSH_GROUP: debugType = "push group"; break; case GL_DEBUG_TYPE_POP_GROUP: debugType = "pop group"; break; } switch (severity) { case GL_DEBUG_SEVERITY_HIGH: debugSeverity = "high"; break; case GL_DEBUG_SEVERITY_MEDIUM: debugSeverity = "medium"; break; case GL_DEBUG_SEVERITY_LOW: debugSeverity = "low"; break; case GL_DEBUG_SEVERITY_NOTIFICATION: debugSeverity = "notification"; break; } if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { debug_printf( "OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message); } else { LOGWARNING( "OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message); } } } // anonymous namespace // static std::unique_ptr CDevice::Create(SDL_Window* window, const bool arb) { std::unique_ptr device(new CDevice()); if (window) { // According to https://wiki.libsdl.org/SDL_CreateWindow we don't need to // call SDL_GL_LoadLibrary if we have a window with SDL_WINDOW_OPENGL, // because it'll be called internally for the first created window. device->m_Window = window; device->m_Context = SDL_GL_CreateContext(device->m_Window); if (!device->m_Context) { LOGERROR("SDL_GL_CreateContext failed: '%s'", SDL_GetError()); return nullptr; } #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES ogl_Init(SDL_GL_GetProcAddress, GetX11Display(device->m_Window)); #else ogl_Init(SDL_GL_GetProcAddress); #endif } else { #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES ogl_Init(SDL_GL_GetProcAddress, XOpenDisplay(NULL)); #else ogl_Init(SDL_GL_GetProcAddress); #endif #if OS_WIN || defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES // Hack to stop things looking very ugly when scrolling in Atlas. ogl_SetVsyncEnabled(true); #endif } // If we don't have GL2.0 then we don't have GLSL in core. if (!arb && !ogl_HaveVersion(2, 0)) return nullptr; if ((ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr) // ARB && !ogl_HaveVersion(2, 0)) // GLSL || !ogl_HaveExtension("GL_ARB_vertex_buffer_object") // VBO || ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", nullptr) || (!ogl_HaveExtension("GL_EXT_framebuffer_object") && !ogl_HaveExtension("GL_ARB_framebuffer_object"))) { // It doesn't make sense to continue working here, because we're not // able to display anything. DEBUG_DISPLAY_FATAL_ERROR( L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders." L" The game does not support pre-shader graphics cards." L" You are advised to try installing newer drivers and/or upgrade your graphics card." L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734" ); } device->m_ARB = arb; device->m_Name = GetNameImpl(); device->m_Version = GetVersionImpl(); device->m_DriverInformation = GetDriverInformationImpl(); device->m_Extensions = GetExtensionsImpl(); // Set packing parameters for uploading and downloading data. glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glEnable(GL_TEXTURE_2D); if (arb) { #if !CONFIG2_GLES glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); #endif } device->m_Backbuffer = CFramebuffer::CreateBackbuffer(device.get()); Capabilities& capabilities = device->m_Capabilities; capabilities.ARBShaders = !ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr); if (capabilities.ARBShaders) capabilities.ARBShadersShadow = ogl_HaveExtension("GL_ARB_fragment_program_shadow"); capabilities.computeShaders = ogl_HaveVersion(4, 3) || ogl_HaveExtension("GL_ARB_compute_shader"); #if CONFIG2_GLES // Some GLES implementations have GL_EXT_texture_compression_dxt1 // but that only supports DXT1 so we can't use it. capabilities.S3TC = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", nullptr) == 0; #else // Note: we don't bother checking for GL_S3_s3tc - it is incompatible // and irrelevant (was never widespread). capabilities.S3TC = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", nullptr) == 0; #endif #if CONFIG2_GLES capabilities.multisampling = false; capabilities.maxSampleCount = 1; #else capabilities.multisampling = ogl_HaveVersion(3, 3) && ogl_HaveExtension("GL_ARB_multisample") && ogl_HaveExtension("GL_ARB_texture_multisample"); if (capabilities.multisampling) { // By default GL_MULTISAMPLE should be enabled, but enable it for buggy drivers. glEnable(GL_MULTISAMPLE); GLint maxSamples = 1; glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); capabilities.maxSampleCount = maxSamples; } #endif capabilities.anisotropicFiltering = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"); if (capabilities.anisotropicFiltering) { GLfloat maxAnisotropy = 1.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy); capabilities.maxAnisotropy = maxAnisotropy; } GLint maxTextureSize = 1024; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); capabilities.maxTextureSize = maxTextureSize; #if CONFIG2_GLES const bool isDebugInCore = ogl_HaveVersion(3, 2); #else const bool isDebugInCore = ogl_HaveVersion(4, 3); #endif const bool hasDebug = isDebugInCore || ogl_HaveExtension("GL_KHR_debug"); if (hasDebug) { #ifdef NDEBUG bool enableDebugMessages = false; CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages); capabilities.debugLabels = false; CFG_GET_VAL("renderer.backend.debuglabels", capabilities.debugLabels); capabilities.debugScopedLabels = false; CFG_GET_VAL("renderer.backend.debugscopedlabels", capabilities.debugScopedLabels); #else const bool enableDebugMessages = true; capabilities.debugLabels = true; capabilities.debugScopedLabels = true; #endif if (enableDebugMessages) { glEnable(GL_DEBUG_OUTPUT); #if !CONFIG2_GLES glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); #else #warning GLES without GL_DEBUG_OUTPUT_SYNCHRONOUS might call the callback from different threads which might be unsafe. #endif glDebugMessageCallback(OnDebugMessage, nullptr); // Filter out our own debug group messages const GLuint id = 0x0AD; glDebugMessageControl( GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE); glDebugMessageControl( GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE); } } #if CONFIG2_GLES capabilities.instancing = false; #else capabilities.instancing = !device->m_ARB && (ogl_HaveVersion(3, 3) || (ogl_HaveExtension("GL_ARB_draw_instanced") && ogl_HaveExtension("GL_ARB_instanced_arrays"))); #endif return device; } CDevice::CDevice() = default; CDevice::~CDevice() { if (m_Context) SDL_GL_DeleteContext(m_Context); } void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { const char* errstr = "(error)"; Script::SetProperty(rq, settings, "name", m_ARB ? "glarb" : "gl"); #define INTEGER(id) do { \ GLint i = -1; \ glGetIntegerv(GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #id, i); \ } while (false) #define INTEGER2(id) do { \ GLint i[2] = { -1, -1 }; \ glGetIntegerv(GL_##id, i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \ } else { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", i[0]); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", i[1]); \ } \ } while (false) #define FLOAT(id) do { \ GLfloat f = std::numeric_limits::quiet_NaN(); \ glGetFloatv(GL_##id, &f); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #id, f); \ } while (false) #define FLOAT2(id) do { \ GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \ glGetFloatv(GL_##id, f); \ if (ogl_SquelchError(GL_INVALID_ENUM)) { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \ } else { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", f[0]); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", f[1]); \ } \ } while (false) #define STRING(id) do { \ const char* c = (const char*)glGetString(GL_##id); \ if (!c) c = ""; \ if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \ Script::SetProperty(rq, settings, "GL_" #id, std::string(c)); \ } while (false) #define QUERY(target, pname) do { \ GLint i = -1; \ glGetQueryivARB(GL_##target, GL_##pname, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, i); \ } while (false) #define VERTEXPROGRAM(id) do { \ GLint i = -1; \ glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \ } while (false) #define FRAGMENTPROGRAM(id) do { \ GLint i = -1; \ glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \ } while (false) #define BOOL(id) INTEGER(id) ogl_WarnIfError(); // Core OpenGL 1.3: // (We don't bother checking extension strings for anything older than 1.3; // it'll just produce harmless warnings) STRING(VERSION); STRING(VENDOR); STRING(RENDERER); STRING(EXTENSIONS); #if !CONFIG2_GLES INTEGER(MAX_CLIP_PLANES); #endif INTEGER(SUBPIXEL_BITS); #if !CONFIG2_GLES INTEGER(MAX_3D_TEXTURE_SIZE); #endif INTEGER(MAX_TEXTURE_SIZE); INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE); INTEGER2(MAX_VIEWPORT_DIMS); #if !CONFIG2_GLES BOOL(RGBA_MODE); BOOL(INDEX_MODE); BOOL(DOUBLEBUFFER); BOOL(STEREO); #endif FLOAT2(ALIASED_POINT_SIZE_RANGE); FLOAT2(ALIASED_LINE_WIDTH_RANGE); #if !CONFIG2_GLES INTEGER(MAX_ELEMENTS_INDICES); INTEGER(MAX_ELEMENTS_VERTICES); INTEGER(MAX_TEXTURE_UNITS); #endif INTEGER(SAMPLE_BUFFERS); INTEGER(SAMPLES); // TODO: compressed texture formats INTEGER(RED_BITS); INTEGER(GREEN_BITS); INTEGER(BLUE_BITS); INTEGER(ALPHA_BITS); #if !CONFIG2_GLES INTEGER(INDEX_BITS); #endif INTEGER(DEPTH_BITS); INTEGER(STENCIL_BITS); #if !CONFIG2_GLES // Core OpenGL 2.0 (treated as extensions): if (ogl_HaveExtension("GL_EXT_texture_lod_bias")) { FLOAT(MAX_TEXTURE_LOD_BIAS_EXT); } if (ogl_HaveExtension("GL_ARB_occlusion_query")) { QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_ARB_shading_language_100")) { STRING(SHADING_LANGUAGE_VERSION_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_shader")) { INTEGER(MAX_VERTEX_ATTRIBS_ARB); INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB); INTEGER(MAX_VARYING_FLOATS_ARB); INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB); } if (ogl_HaveExtension("GL_ARB_fragment_shader")) { INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") || ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program")) { INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_TEXTURE_COORDS_ARB); } if (ogl_HaveExtension("GL_ARB_draw_buffers")) { INTEGER(MAX_DRAW_BUFFERS_ARB); } // Core OpenGL 3.0: if (ogl_HaveExtension("GL_EXT_gpu_shader4")) { INTEGER(MIN_PROGRAM_TEXEL_OFFSET_EXT); // no _EXT version of these in glext.h INTEGER(MAX_PROGRAM_TEXEL_OFFSET_EXT); } if (ogl_HaveExtension("GL_EXT_framebuffer_object")) { INTEGER(MAX_COLOR_ATTACHMENTS_EXT); INTEGER(MAX_RENDERBUFFER_SIZE_EXT); } if (ogl_HaveExtension("GL_EXT_framebuffer_multisample")) { INTEGER(MAX_SAMPLES_EXT); } if (ogl_HaveExtension("GL_EXT_texture_array")) { INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT); } if (ogl_HaveExtension("GL_EXT_transform_feedback")) { INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT); INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT); INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT); } // Other interesting extensions: if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query")) { QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_ARB_timer_query")) { QUERY(TIMESTAMP, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic")) { FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT); } if (ogl_HaveExtension("GL_ARB_texture_rectangle")) { INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB); } if (m_ARB) { if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program")) { INTEGER(MAX_PROGRAM_MATRICES_ARB); INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_program")) { VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB); VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB); VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB); if (ogl_HaveExtension("GL_ARB_fragment_program")) { // The spec seems to say these should be supported, but // Mesa complains about them so let's not bother /* VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB); */ } } if (ogl_HaveExtension("GL_ARB_fragment_program")) { FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB); if (ogl_HaveExtension("GL_ARB_vertex_program")) { // The spec seems to say these should be supported, but // Intel drivers on Windows complain about them so let's not bother /* FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB); */ } } } if (ogl_HaveExtension("GL_ARB_geometry_shader4")) { INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB); INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB); INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB); INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB); INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB); } #else // CONFIG2_GLES // Core OpenGL ES 2.0: STRING(SHADING_LANGUAGE_VERSION); INTEGER(MAX_VERTEX_ATTRIBS); INTEGER(MAX_VERTEX_UNIFORM_VECTORS); INTEGER(MAX_VARYING_VECTORS); INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS); INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS); INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS); INTEGER(MAX_TEXTURE_IMAGE_UNITS); INTEGER(MAX_RENDERBUFFER_SIZE); #endif // CONFIG2_GLES // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES #define GLXQCR_INTEGER(id) do { \ unsigned int i = UINT_MAX; \ if (glXQueryCurrentRendererIntegerMESA(id, &i)) \ Script::SetProperty(rq, settings, #id, i); \ } while (false) #define GLXQCR_INTEGER2(id) do { \ unsigned int i[2] = { UINT_MAX, UINT_MAX }; \ if (glXQueryCurrentRendererIntegerMESA(id, i)) { \ Script::SetProperty(rq, settings, #id "[0]", i[0]); \ Script::SetProperty(rq, settings, #id "[1]", i[1]); \ } \ } while (false) #define GLXQCR_INTEGER3(id) do { \ unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \ if (glXQueryCurrentRendererIntegerMESA(id, i)) { \ Script::SetProperty(rq, settings, #id "[0]", i[0]); \ Script::SetProperty(rq, settings, #id "[1]", i[1]); \ Script::SetProperty(rq, settings, #id "[2]", i[2]); \ } \ } while (false) #define GLXQCR_STRING(id) do { \ const char* str = glXQueryCurrentRendererStringMESA(id); \ if (str) \ Script::SetProperty(rq, settings, #id ".string", str); \ } while (false) SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); const int ret = SDL_GetWindowWMInfo(m_Window, &wminfo); if (ret && wminfo.subsystem == SDL_SYSWM_X11) { Display* dpy = wminfo.info.x11.display; int scrnum = DefaultScreen(dpy); const char* glxexts = glXQueryExtensionsString(dpy, scrnum); Script::SetProperty(rq, settings, "glx_extensions", glxexts); if (strstr(glxexts, "GLX_MESA_query_renderer") && glXQueryCurrentRendererIntegerMESA && glXQueryCurrentRendererStringMESA) { GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA); GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA); GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA); GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA); GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA); GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA); GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA); GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA); GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA); } } #endif // SDL_VIDEO_DRIVER_X11 } std::unique_ptr CDevice::CreateCommandContext() { std::unique_ptr commandContet = CDeviceCommandContext::Create(this); m_ActiveCommandContext = commandContet.get(); return commandContet; } std::unique_ptr CDevice::CreateTexture(const char* name, const ITexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CTexture::Create(this, name, type, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture(name, CTexture::Type::TEXTURE_2D, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) { return CreateFramebuffer(name, colorAttachment, depthStencilAttachment, CColor(0.0f, 0.0f, 0.0f, 0.0f)); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) { return CFramebuffer::Create( this, name, colorAttachment->As(), depthStencilAttachment->As(), clearColor); } std::unique_ptr CDevice::CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { return CBuffer::Create(this, name, type, size, dynamic); } std::unique_ptr CDevice::CreateShaderProgram( const CStr& name, const CShaderDefines& defines) { return CShaderProgram::Create(this, name, defines); } void CDevice::Present() { if (m_Window) { PROFILE3("swap buffers"); SDL_GL_SwapWindow(m_Window); ogl_WarnIfError(); } bool checkGLErrorAfterSwap = false; CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap); #if defined(NDEBUG) if (!checkGLErrorAfterSwap) return; #endif PROFILE3("error check"); // We have to check GL errors after SwapBuffer to avoid possible // synchronizations during rendering. if (GLenum err = glGetError()) ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err)); } bool CDevice::IsTextureFormatSupported(const Format format) const { bool supported = false; switch (format) { case Format::UNDEFINED: break; case Format::R8G8B8_UNORM: FALLTHROUGH; case Format::R8G8B8A8_UNORM: FALLTHROUGH; case Format::A8_UNORM: FALLTHROUGH; case Format::L8_UNORM: supported = true; break; case Format::R32_SFLOAT: FALLTHROUGH; case Format::R32G32_SFLOAT: FALLTHROUGH; case Format::R32G32B32_SFLOAT: FALLTHROUGH; case Format::R32G32B32A32_SFLOAT: break; case Format::D16: FALLTHROUGH; case Format::D24: FALLTHROUGH; case Format::D32: supported = true; break; case Format::D24_S8: #if !CONFIG2_GLES supported = true; #endif break; case Format::BC1_RGB_UNORM: FALLTHROUGH; case Format::BC1_RGBA_UNORM: FALLTHROUGH; case Format::BC2_UNORM: FALLTHROUGH; case Format::BC3_UNORM: supported = m_Capabilities.S3TC; break; default: break; } return supported; } +bool CDevice::IsFramebufferFormatSupported(const Format format) const +{ + bool supported = false; + switch (format) + { + case Format::UNDEFINED: + break; +#if !CONFIG2_GLES + case Format::R8_UNORM: + supported = ogl_HaveVersion(3, 0); + break; +#endif + case Format::R8G8B8A8_UNORM: + supported = true; + break; + default: + break; + } + return supported; +} + } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Device.h =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.h (revision 26905) +++ ps/trunk/source/renderer/backend/gl/Device.h (revision 26906) @@ -1,127 +1,129 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_GL_DEVICE #define INCLUDED_RENDERER_BACKEND_GL_DEVICE #include "renderer/backend/Format.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/gl/Framebuffer.h" #include "renderer/backend/gl/ShaderProgram.h" #include "renderer/backend/gl/Texture.h" #include "renderer/backend/IDevice.h" #include "scriptinterface/ScriptForward.h" #include #include #include typedef struct SDL_Window SDL_Window; typedef void* SDL_GLContext; namespace Renderer { namespace Backend { namespace GL { class CDeviceCommandContext; class CDevice final : public IDevice { public: ~CDevice() override; /** * Creates the GL device and the GL context for the window if it presents. */ static std::unique_ptr Create(SDL_Window* window, const bool arb); const std::string& GetName() const override { return m_Name; } const std::string& GetVersion() const override { return m_Version; } const std::string& GetDriverInformation() const override { return m_DriverInformation; } const std::vector& GetExtensions() const override { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings) override; IFramebuffer* GetCurrentBackbuffer() override { return m_Backbuffer.get(); } std::unique_ptr CreateCommandContext() override; CDeviceCommandContext* GetActiveCommandContext() { return m_ActiveCommandContext; } std::unique_ptr CreateTexture(const char* name, const ITexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) override; std::unique_ptr CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) override; std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; void Present() override; bool IsTextureFormatSupported(const Format format) const override; + bool IsFramebufferFormatSupported(const Format format) const override; + const Capabilities& GetCapabilities() const override { return m_Capabilities; } private: CDevice(); SDL_Window* m_Window = nullptr; SDL_GLContext m_Context = nullptr; bool m_ARB = false; std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; // GL can have the only one command context at once. // TODO: remove as soon as we have no GL code outside backend, currently // it's used only as a helper for transition. CDeviceCommandContext* m_ActiveCommandContext = nullptr; std::unique_ptr m_Backbuffer; Capabilities m_Capabilities{}; }; } // namespace GL } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_GL_DEVICE Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26906) @@ -1,1169 +1,1192 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "DeviceCommandContext.h" #include "ps/CLogger.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/Framebuffer.h" #include "renderer/backend/gl/Mapping.h" #include "renderer/backend/gl/ShaderProgram.h" #include "renderer/backend/gl/Texture.h" #include #include #include namespace Renderer { namespace Backend { namespace GL { namespace { bool operator==(const StencilOpState& lhs, const StencilOpState& rhs) { return lhs.failOp == rhs.failOp && lhs.passOp == rhs.passOp && lhs.depthFailOp == rhs.depthFailOp && lhs.compareOp == rhs.compareOp; } bool operator!=(const StencilOpState& lhs, const StencilOpState& rhs) { return !operator==(lhs, rhs); } bool operator==( const CDeviceCommandContext::Rect& lhs, const CDeviceCommandContext::Rect& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.width == rhs.width && lhs.height == rhs.height; } bool operator!=( const CDeviceCommandContext::Rect& lhs, const CDeviceCommandContext::Rect& rhs) { return !operator==(lhs, rhs); } void ApplyDepthMask(const bool depthWriteEnabled) { glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE); } void ApplyColorMask(const uint8_t colorWriteMask) { glColorMask( (colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE); } void ApplyStencilMask(const uint32_t stencilWriteMask) { glStencilMask(stencilWriteMask); } GLenum BufferTypeToGLTarget(const CBuffer::Type type) { GLenum target = GL_ARRAY_BUFFER; switch (type) { case CBuffer::Type::VERTEX: target = GL_ARRAY_BUFFER; break; case CBuffer::Type::INDEX: target = GL_ELEMENT_ARRAY_BUFFER; break; }; return target; } #if !CONFIG2_GLES bool IsDepthTexture(const Format format) { return format == Format::D16 || format == Format::D24 || format == Format::D32 || format == Format::D24_S8; } #endif // !CONFIG2_GLES void UploadDynamicBufferRegionImpl( const GLenum target, const uint32_t bufferSize, const uint32_t dataOffset, const uint32_t dataSize, const CDeviceCommandContext::UploadBufferFunction& uploadFunction) { ENSURE(dataOffset < dataSize); // Tell the driver that it can reallocate the whole VBO glBufferDataARB(target, bufferSize, nullptr, GL_DYNAMIC_DRAW); ogl_WarnIfError(); while (true) { // (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used // here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with // current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do // the explicit glBufferData.) void* mappedData = glMapBufferARB(target, GL_WRITE_ONLY); if (mappedData == nullptr) { // This shouldn't happen unless we run out of virtual address space LOGERROR("glMapBuffer failed"); break; } uploadFunction(static_cast(mappedData) + dataOffset); if (glUnmapBufferARB(target) == GL_TRUE) break; // Unmap might fail on e.g. resolution switches, so just try again // and hope it will eventually succeed LOGMESSAGE("glUnmapBuffer failed, trying again...\n"); } } } // anonymous namespace // static std::unique_ptr CDeviceCommandContext::Create(CDevice* device) { std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device)); deviceCommandContext->m_Framebuffer = static_cast(device->GetCurrentBackbuffer()); deviceCommandContext->ResetStates(); return deviceCommandContext; } CDeviceCommandContext::CDeviceCommandContext(CDevice* device) : m_Device(device) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); for (BindUnit& unit : m_BoundTextures) { unit.target = GL_TEXTURE_2D; unit.handle = 0; } for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { m_VertexAttributeFormat[index].active = false; m_VertexAttributeFormat[index].initialized = false; m_VertexAttributeFormat[index].bindingSlot = 0; } for (size_t index = 0; index < m_BoundBuffers.size(); ++index) { const CBuffer::Type type = static_cast(index); const GLenum target = BufferTypeToGLTarget(type); const GLuint handle = 0; m_BoundBuffers[index].first = target; m_BoundBuffers[index].second = handle; } } CDeviceCommandContext::~CDeviceCommandContext() = default; IDevice* CDeviceCommandContext::GetDevice() { return m_Device; } void CDeviceCommandContext::SetGraphicsPipelineState( const GraphicsPipelineStateDesc& pipelineStateDesc) { SetGraphicsPipelineStateImpl(pipelineStateDesc, false); } void CDeviceCommandContext::UploadTexture( ITexture* texture, const Format format, const void* data, const size_t dataSize, const uint32_t level, const uint32_t layer) { UploadTextureRegion(texture, format, data, dataSize, 0, 0, std::max(1u, texture->GetWidth() >> level), std::max(1u, texture->GetHeight() >> level), level, layer); } void CDeviceCommandContext::UploadTextureRegion( ITexture* destinationTexture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height, const uint32_t level, const uint32_t layer) { ENSURE(destinationTexture); CTexture* texture = destinationTexture->As(); ENSURE(width > 0 && height > 0); if (texture->GetType() == CTexture::Type::TEXTURE_2D) { ENSURE(layer == 0); if (texture->GetFormat() == Format::R8G8B8A8_UNORM || texture->GetFormat() == Format::R8G8B8_UNORM || +#if !CONFIG2_GLES + texture->GetFormat() == Format::R8_UNORM || +#endif texture->GetFormat() == Format::A8_UNORM) { ENSURE(texture->GetFormat() == dataFormat); size_t bytesPerPixel = 4; GLenum pixelFormat = GL_RGBA; switch (dataFormat) { case Format::R8G8B8A8_UNORM: break; case Format::R8G8B8_UNORM: pixelFormat = GL_RGB; bytesPerPixel = 3; break; +#if !CONFIG2_GLES + case Format::R8_UNORM: + pixelFormat = GL_RED; + bytesPerPixel = 1; + break; +#endif case Format::A8_UNORM: pixelFormat = GL_ALPHA; bytesPerPixel = 1; break; case Format::L8_UNORM: pixelFormat = GL_LUMINANCE; bytesPerPixel = 1; break; default: debug_warn("Unexpected format."); break; } ENSURE(dataSize == width * height * bytesPerPixel); ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, level, xOffset, yOffset, width, height, pixelFormat, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } else if ( texture->GetFormat() == Format::BC1_RGB_UNORM || texture->GetFormat() == Format::BC1_RGBA_UNORM || texture->GetFormat() == Format::BC2_UNORM || texture->GetFormat() == Format::BC3_UNORM) { ENSURE(xOffset == 0 && yOffset == 0); ENSURE(texture->GetFormat() == dataFormat); // TODO: add data size check. GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; switch (texture->GetFormat()) { case Format::BC1_RGBA_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; case Format::BC2_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case Format::BC3_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: break; } ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle()); glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data); ogl_WarnIfError(); } else debug_warn("Unsupported format"); } else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE) { if (texture->GetFormat() == Format::R8G8B8A8_UNORM) { ENSURE(texture->GetFormat() == dataFormat); ENSURE(level == 0 && layer < 6); ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height); const size_t bpp = 4; ENSURE(dataSize == width * height * bpp); // The order of layers should be the following: // front, back, top, bottom, right, left static const GLenum targets[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle()); glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } else debug_warn("Unsupported format"); } else debug_warn("Unsupported type"); } void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) { UploadBufferRegion(buffer, data, dataSize, 0); } void CDeviceCommandContext::UploadBuffer( IBuffer* buffer, const UploadBufferFunction& uploadFunction) { UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction); } void CDeviceCommandContext::UploadBufferRegion( IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) { ENSURE(data); ENSURE(dataOffset + dataSize <= buffer->GetSize()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); ScopedBufferBind scopedBufferBind(this, buffer->As()); if (buffer->IsDynamic()) { UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, [data, dataSize](u8* mappedData) { std::memcpy(mappedData, data, dataSize); }); } else { glBufferSubDataARB(target, dataOffset, dataSize, data); ogl_WarnIfError(); } } void CDeviceCommandContext::UploadBufferRegion( IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, const UploadBufferFunction& uploadFunction) { ENSURE(dataOffset + dataSize <= buffer->GetSize()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); ScopedBufferBind scopedBufferBind(this, buffer->As()); ENSURE(buffer->IsDynamic()); UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, uploadFunction); } void CDeviceCommandContext::BeginScopedLabel(const char* name) { if (!m_Device->GetCapabilities().debugScopedLabels) return; ++m_ScopedLabelDepth; glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name); } void CDeviceCommandContext::EndScopedLabel() { if (!m_Device->GetCapabilities().debugScopedLabels) return; ENSURE(m_ScopedLabelDepth > 0); --m_ScopedLabelDepth; glPopDebugGroup(); } void CDeviceCommandContext::BindTexture( const uint32_t unit, const GLenum target, const GLuint handle) { ENSURE(unit < m_BoundTextures.size()); #if CONFIG2_GLES ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP); #else ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE); #endif if (m_ActiveTextureUnit != unit) { glActiveTexture(GL_TEXTURE0 + unit); m_ActiveTextureUnit = unit; } if (m_BoundTextures[unit].target == target && m_BoundTextures[unit].handle == handle) return; if (m_BoundTextures[unit].target != target && m_BoundTextures[unit].target && m_BoundTextures[unit].handle) glBindTexture(m_BoundTextures[unit].target, 0); if (m_BoundTextures[unit].handle != handle) glBindTexture(target, handle); ogl_WarnIfError(); m_BoundTextures[unit] = {target, handle}; } void CDeviceCommandContext::BindBuffer(const IBuffer::Type type, CBuffer* buffer) { ENSURE(!buffer || buffer->GetType() == type); if (type == IBuffer::Type::VERTEX) { if (m_VertexBuffer == buffer) return; m_VertexBuffer = buffer; } else if (type == IBuffer::Type::INDEX) { if (!buffer) m_IndexBuffer = nullptr; m_IndexBufferData = nullptr; } const GLenum target = BufferTypeToGLTarget(type); const GLuint handle = buffer ? buffer->GetHandle() : 0; glBindBufferARB(target, handle); ogl_WarnIfError(); const size_t cacheIndex = static_cast(type); ENSURE(cacheIndex < m_BoundBuffers.size()); m_BoundBuffers[cacheIndex].second = handle; } void CDeviceCommandContext::OnTextureDestroy(CTexture* texture) { ENSURE(texture); for (size_t index = 0; index < m_BoundTextures.size(); ++index) if (m_BoundTextures[index].handle == texture->GetHandle()) BindTexture(index, GL_TEXTURE_2D, 0); } void CDeviceCommandContext::Flush() { ENSURE(m_ScopedLabelDepth == 0); GPU_SCOPED_LABEL(this, "CDeviceCommandContext::Flush"); ResetStates(); m_IndexBuffer = nullptr; m_IndexBufferData = nullptr; for (size_t unit = 0; unit < m_BoundTextures.size(); ++unit) { if (m_BoundTextures[unit].handle) BindTexture(unit, GL_TEXTURE_2D, 0); } BindBuffer(CBuffer::Type::INDEX, nullptr); BindBuffer(CBuffer::Type::VERTEX, nullptr); } void CDeviceCommandContext::ResetStates() { SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true); SetScissors(0, nullptr); SetFramebuffer(m_Device->GetCurrentBackbuffer()); } void CDeviceCommandContext::SetGraphicsPipelineStateImpl( const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force) { ENSURE(!m_InsidePass); if (m_GraphicsPipelineStateDesc.shaderProgram != pipelineStateDesc.shaderProgram) { CShaderProgram* currentShaderProgram = nullptr; if (m_GraphicsPipelineStateDesc.shaderProgram) { currentShaderProgram = static_cast(m_GraphicsPipelineStateDesc.shaderProgram); } CShaderProgram* nextShaderProgram = nullptr; if (pipelineStateDesc.shaderProgram) { nextShaderProgram = static_cast(pipelineStateDesc.shaderProgram); for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { const VertexAttributeStream stream = static_cast(index); m_VertexAttributeFormat[index].active = nextShaderProgram->IsStreamActive(stream); m_VertexAttributeFormat[index].initialized = false; m_VertexAttributeFormat[index].bindingSlot = std::numeric_limits::max(); } } if (nextShaderProgram) nextShaderProgram->Bind(currentShaderProgram); else if (currentShaderProgram) currentShaderProgram->Unbind(); m_ShaderProgram = nextShaderProgram; } const DepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState; const DepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState; if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled) { if (nextDepthStencilStateDesc.depthTestEnabled) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); } if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp) { glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp)); } if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled) { ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled); } if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled) { if (nextDepthStencilStateDesc.stencilTestEnabled) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); } if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace) { if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace) { glStencilOp( Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp)); } else { if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace) { glStencilOpSeparate( GL_FRONT, Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp)); } if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace) { glStencilOpSeparate( GL_BACK, Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp)); } } } if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask) { ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask); } if (force || currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference || currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask || currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp || currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp) { if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp) { glStencilFunc( Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); } else { glStencilFuncSeparate(GL_FRONT, Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); glStencilFuncSeparate(GL_BACK, Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); } } const BlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState; const BlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState; if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled) { if (nextBlendStateDesc.enabled) glEnable(GL_BLEND); else glDisable(GL_BLEND); } if (force || currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor || currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor || currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor || currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor) { if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor && nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor) { glBlendFunc( Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor)); } else { glBlendFuncSeparate( Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor)); } } if (force || currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp || currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp) { if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp) { glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp)); } else { glBlendEquationSeparate( Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp), Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp)); } } if (force || currentBlendStateDesc.constant != nextBlendStateDesc.constant) { glBlendColor( nextBlendStateDesc.constant.r, nextBlendStateDesc.constant.g, nextBlendStateDesc.constant.b, nextBlendStateDesc.constant.a); } if (force || currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask) { ApplyColorMask(nextBlendStateDesc.colorWriteMask); } const RasterizationStateDesc& currentRasterizationStateDesc = m_GraphicsPipelineStateDesc.rasterizationState; const RasterizationStateDesc& nextRasterizationStateDesc = pipelineStateDesc.rasterizationState; if (force || currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode) { #if !CONFIG2_GLES glPolygonMode( GL_FRONT_AND_BACK, nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL); #endif } if (force || currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode) { if (nextRasterizationStateDesc.cullMode == CullMode::NONE) { glDisable(GL_CULL_FACE); } else { if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE) glEnable(GL_CULL_FACE); glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK); } } if (force || currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace) { if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE) glFrontFace(GL_CW); else glFrontFace(GL_CCW); } #if !CONFIG2_GLES if (force || currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled) { if (nextRasterizationStateDesc.depthBiasEnabled) glEnable(GL_POLYGON_OFFSET_FILL); else glDisable(GL_POLYGON_OFFSET_FILL); } if (force || currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor || currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor) { glPolygonOffset( nextRasterizationStateDesc.depthBiasSlopeFactor, nextRasterizationStateDesc.depthBiasConstantFactor); } #endif ogl_WarnIfError(); m_GraphicsPipelineStateDesc = pipelineStateDesc; } void CDeviceCommandContext::BlitFramebuffer( IFramebuffer* dstFramebuffer, IFramebuffer* srcFramebuffer) { CFramebuffer* destinationFramebuffer = dstFramebuffer->As(); CFramebuffer* sourceFramebuffer = srcFramebuffer->As(); #if CONFIG2_GLES UNUSED2(destinationFramebuffer); UNUSED2(sourceFramebuffer); debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES"); #else // Source framebuffer should not be backbuffer. ENSURE(sourceFramebuffer->GetHandle() != 0); ENSURE(destinationFramebuffer != sourceFramebuffer); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle()); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle()); // TODO: add more check for internal formats. And currently we don't support // scaling inside blit. glBlitFramebufferEXT( 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(), 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(), (sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()), GL_NEAREST); ogl_WarnIfError(); #endif } void CDeviceCommandContext::ClearFramebuffer() { ClearFramebuffer(true, true, true); } void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil) { const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0; const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0; const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0; GLbitfield mask = 0; if (needsColor) { ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA); glClearColor( m_Framebuffer->GetClearColor().r, m_Framebuffer->GetClearColor().g, m_Framebuffer->GetClearColor().b, m_Framebuffer->GetClearColor().a); mask |= GL_COLOR_BUFFER_BIT; } if (needsDepth) { ApplyDepthMask(true); mask |= GL_DEPTH_BUFFER_BIT; } if (needsStencil) { ApplyStencilMask(std::numeric_limits::max()); mask |= GL_STENCIL_BUFFER_BIT; } glClear(mask); ogl_WarnIfError(); if (needsColor) ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask); if (needsDepth) ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled); if (needsStencil) ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask); } void CDeviceCommandContext::SetFramebuffer(IFramebuffer* framebuffer) { ENSURE(framebuffer); m_Framebuffer = framebuffer->As(); ENSURE(m_Framebuffer->GetHandle() == 0 || (m_Framebuffer->GetWidth() > 0 && m_Framebuffer->GetHeight() > 0)); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_Framebuffer->GetHandle()); ogl_WarnIfError(); } void CDeviceCommandContext::ReadbackFramebufferSync( const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, void* data) { ENSURE(m_Framebuffer); glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors) { ENSURE(scissorCount <= 1); if (scissorCount == 0) { if (m_ScissorCount != scissorCount) glDisable(GL_SCISSOR_TEST); } else { if (m_ScissorCount != scissorCount) glEnable(GL_SCISSOR_TEST); ENSURE(scissors); if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0]) { m_Scissors[0] = scissors[0]; glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height); } } ogl_WarnIfError(); m_ScissorCount = scissorCount; } void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports) { ENSURE(viewportCount == 1); glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height); ogl_WarnIfError(); } void CDeviceCommandContext::SetVertexAttributeFormat( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, const VertexAttributeRate rate, const uint32_t bindingSlot) { const uint32_t index = static_cast(stream); ENSURE(index < m_VertexAttributeFormat.size()); ENSURE(bindingSlot < m_VertexAttributeFormat.size()); if (!m_VertexAttributeFormat[index].active) return; m_VertexAttributeFormat[index].format = format; m_VertexAttributeFormat[index].offset = offset; m_VertexAttributeFormat[index].stride = stride; m_VertexAttributeFormat[index].rate = rate; m_VertexAttributeFormat[index].bindingSlot = bindingSlot; m_VertexAttributeFormat[index].initialized = true; } void CDeviceCommandContext::SetVertexBuffer( const uint32_t bindingSlot, IBuffer* buffer) { ENSURE(buffer); ENSURE(buffer->GetType() == IBuffer::Type::VERTEX); ENSURE(m_ShaderProgram); BindBuffer(buffer->GetType(), buffer->As()); for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot) continue; ENSURE(m_VertexAttributeFormat[index].initialized); const VertexAttributeStream stream = static_cast(index); m_ShaderProgram->VertexAttribPointer(stream, m_VertexAttributeFormat[index].format, m_VertexAttributeFormat[index].offset, m_VertexAttributeFormat[index].stride, m_VertexAttributeFormat[index].rate, nullptr); } } void CDeviceCommandContext::SetVertexBufferData( const uint32_t bindingSlot, const void* data, const uint32_t dataSize) { ENSURE(data); ENSURE(m_ShaderProgram); ENSURE(dataSize > 0); BindBuffer(CBuffer::Type::VERTEX, nullptr); for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index) { if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot) continue; ENSURE(m_VertexAttributeFormat[index].initialized); const VertexAttributeStream stream = static_cast(index); // We don't know how many vertices will be used in a draw command, so we // assume at least one vertex. ENSURE(dataSize >= m_VertexAttributeFormat[index].offset + m_VertexAttributeFormat[index].stride); m_ShaderProgram->VertexAttribPointer(stream, m_VertexAttributeFormat[index].format, m_VertexAttributeFormat[index].offset, m_VertexAttributeFormat[index].stride, m_VertexAttributeFormat[index].rate, data); } } void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer) { ENSURE(buffer->GetType() == CBuffer::Type::INDEX); m_IndexBuffer = buffer->As(); m_IndexBufferData = nullptr; BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer); } void CDeviceCommandContext::SetIndexBufferData(const void* data, const uint32_t dataSize) { ENSURE(dataSize > 0); if (m_IndexBuffer) { BindBuffer(CBuffer::Type::INDEX, nullptr); m_IndexBuffer = nullptr; } m_IndexBufferData = data; } void CDeviceCommandContext::BeginPass() { ENSURE(!m_InsidePass); m_InsidePass = true; } void CDeviceCommandContext::EndPass() { ENSURE(m_InsidePass); m_InsidePass = false; } void CDeviceCommandContext::Draw( const uint32_t firstVertex, const uint32_t vertexCount) { ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); // Some drivers apparently don't like count = 0 in glDrawArrays here, so skip // all drawing in that case. if (vertexCount == 0) return; m_ShaderProgram->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); ogl_WarnIfError(); } void CDeviceCommandContext::DrawIndexed( const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) { ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); if (indexCount == 0) return; ENSURE(m_IndexBuffer || m_IndexBufferData); ENSURE(vertexOffset == 0); if (m_IndexBuffer) { ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize()); } m_ShaderProgram->AssertPointersBound(); // Don't use glMultiDrawElements here since it doesn't have a significant // performance impact and it suffers from various driver bugs (e.g. it breaks // in Mesa 7.10 swrast with index VBOs). glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex))); ogl_WarnIfError(); } void CDeviceCommandContext::DrawInstanced( const uint32_t firstVertex, const uint32_t vertexCount, const uint32_t firstInstance, const uint32_t instanceCount) { ENSURE(m_Device->GetCapabilities().instancing); ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); if (vertexCount == 0 || instanceCount == 0) return; ENSURE(firstInstance == 0); m_ShaderProgram->AssertPointersBound(); +#if CONFIG2_GLES + ENSURE(!m_Device->GetCapabilities().instancing); + UNUSED2(firstVertex); + UNUSED2(vertexCount); + UNUSED2(instanceCount); +#else glDrawArraysInstancedARB(GL_TRIANGLES, firstVertex, vertexCount, instanceCount); +#endif ogl_WarnIfError(); } void CDeviceCommandContext::DrawIndexedInstanced( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t firstInstance, const uint32_t instanceCount, const int32_t vertexOffset) { ENSURE(m_Device->GetCapabilities().instancing); ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); ENSURE(m_IndexBuffer || m_IndexBufferData); if (indexCount == 0) return; ENSURE(firstInstance == 0 && vertexOffset == 0); if (m_IndexBuffer) { ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize()); } m_ShaderProgram->AssertPointersBound(); // Don't use glMultiDrawElements here since it doesn't have a significant // performance impact and it suffers from various driver bugs (e.g. it breaks // in Mesa 7.10 swrast with index VBOs). +#if CONFIG2_GLES + ENSURE(!m_Device->GetCapabilities().instancing); + UNUSED2(indexCount); + UNUSED2(firstIndex); + UNUSED2(instanceCount); +#else glDrawElementsInstancedARB(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)), instanceCount); +#endif ogl_WarnIfError(); } void CDeviceCommandContext::DrawIndexedInRange( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t start, const uint32_t end) { ENSURE(m_ShaderProgram); ENSURE(m_InsidePass); if (indexCount == 0) return; ENSURE(m_IndexBuffer || m_IndexBufferData); const void* indices = static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)); m_ShaderProgram->AssertPointersBound(); // Draw with DrawRangeElements where available, since it might be more // efficient for slow hardware. #if CONFIG2_GLES UNUSED2(start); UNUSED2(end); glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices); #else glDrawRangeElementsEXT(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices); #endif ogl_WarnIfError(); } void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture) { ENSURE(m_ShaderProgram); ENSURE(texture); const CShaderProgram::TextureUnit textureUnit = m_ShaderProgram->GetTextureUnit(bindingSlot); if (!textureUnit.type) return; if (textureUnit.type != GL_SAMPLER_2D && #if !CONFIG2_GLES textureUnit.type != GL_SAMPLER_2D_SHADOW && #endif textureUnit.type != GL_SAMPLER_CUBE) { LOGERROR("CDeviceCommandContext::SetTexture: expected sampler at binding slot"); return; } #if !CONFIG2_GLES if (textureUnit.type == GL_SAMPLER_2D_SHADOW) { if (!IsDepthTexture(texture->GetFormat())) { LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture type (expected depth texture)"); return; } } #endif ENSURE(textureUnit.unit >= 0); const uint32_t unit = textureUnit.unit; if (unit >= m_BoundTextures.size()) { LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture unit (too big)"); return; } BindTexture(unit, textureUnit.target, texture->As()->GetHandle()); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float value) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, value); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW); } void CDeviceCommandContext::SetUniform( const int32_t bindingSlot, PS::span values) { ENSURE(m_ShaderProgram); m_ShaderProgram->SetUniform(bindingSlot, values); } CDeviceCommandContext::ScopedBind::ScopedBind( CDeviceCommandContext* deviceCommandContext, const GLenum target, const GLuint handle) : m_DeviceCommandContext(deviceCommandContext), m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit]), m_ActiveTextureUnit(deviceCommandContext->m_ActiveTextureUnit) { const uint32_t unit = m_DeviceCommandContext->m_BoundTextures.size() - 1; m_DeviceCommandContext->BindTexture(unit, target, handle); } CDeviceCommandContext::ScopedBind::~ScopedBind() { m_DeviceCommandContext->BindTexture( m_ActiveTextureUnit, m_OldBindUnit.target, m_OldBindUnit.handle); } CDeviceCommandContext::ScopedBufferBind::ScopedBufferBind( CDeviceCommandContext* deviceCommandContext, CBuffer* buffer) : m_DeviceCommandContext(deviceCommandContext) { ENSURE(buffer); m_CacheIndex = static_cast(buffer->GetType()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); const GLuint handle = buffer->GetHandle(); if (m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first == target && m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second == handle) { // Use an invalid index as a sign that we don't need to restore the // bound buffer. m_CacheIndex = m_DeviceCommandContext->m_BoundBuffers.size(); } else { glBindBufferARB(target, handle); } } CDeviceCommandContext::ScopedBufferBind::~ScopedBufferBind() { if (m_CacheIndex >= m_DeviceCommandContext->m_BoundBuffers.size()) return; glBindBufferARB( m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first, m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second); } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Framebuffer.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Framebuffer.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/gl/Framebuffer.cpp (revision 26906) @@ -1,179 +1,181 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Framebuffer.h" #include "lib/code_annotation.h" #include "lib/config2.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/Texture.h" namespace Renderer { namespace Backend { namespace GL { // static std::unique_ptr CFramebuffer::Create( CDevice* device, const char* name, CTexture* colorAttachment, CTexture* depthStencilAttachment, const CColor& clearColor) { ENSURE(colorAttachment || depthStencilAttachment); std::unique_ptr framebuffer(new CFramebuffer()); framebuffer->m_Device = device; framebuffer->m_ClearColor = clearColor; glGenFramebuffersEXT(1, &framebuffer->m_Handle); if (!framebuffer->m_Handle) { LOGERROR("Failed to create CFramebuffer object"); return nullptr; } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->m_Handle); if (colorAttachment) { + ENSURE(device->IsFramebufferFormatSupported(colorAttachment->GetFormat())); + framebuffer->m_AttachmentMask |= GL_COLOR_BUFFER_BIT; #if CONFIG2_GLES ENSURE(colorAttachment->GetType() == CTexture::Type::TEXTURE_2D); const GLenum textureTarget = GL_TEXTURE_2D; #else const GLenum textureTarget = colorAttachment->GetType() == CTexture::Type::TEXTURE_2D_MULTISAMPLE ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; #endif glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textureTarget, colorAttachment->GetHandle(), 0); } if (depthStencilAttachment) { framebuffer->m_Width = depthStencilAttachment->GetWidth(); framebuffer->m_Height = depthStencilAttachment->GetHeight(); framebuffer->m_AttachmentMask |= GL_DEPTH_BUFFER_BIT; if (depthStencilAttachment->GetFormat() == Format::D24_S8) framebuffer->m_AttachmentMask |= GL_STENCIL_BUFFER_BIT; if (colorAttachment) { ENSURE(colorAttachment->GetWidth() == depthStencilAttachment->GetWidth()); ENSURE(colorAttachment->GetHeight() == depthStencilAttachment->GetHeight()); ENSURE(colorAttachment->GetType() == depthStencilAttachment->GetType()); } ENSURE( depthStencilAttachment->GetFormat() == Format::D16 || depthStencilAttachment->GetFormat() == Format::D24 || depthStencilAttachment->GetFormat() == Format::D32 || depthStencilAttachment->GetFormat() == Format::D24_S8); #if CONFIG2_GLES ENSURE(depthStencilAttachment->GetFormat() != Format::D24_S8); const GLenum attachment = GL_DEPTH_ATTACHMENT; ENSURE(depthStencilAttachment->GetType() == CTexture::Type::TEXTURE_2D); const GLenum textureTarget = GL_TEXTURE_2D; #else const GLenum attachment = depthStencilAttachment->GetFormat() == Format::D24_S8 ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; const GLenum textureTarget = depthStencilAttachment->GetType() == CTexture::Type::TEXTURE_2D_MULTISAMPLE ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; #endif glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, textureTarget, depthStencilAttachment->GetHandle(), 0); } else { framebuffer->m_Width = colorAttachment->GetWidth(); framebuffer->m_Height = colorAttachment->GetHeight(); } ogl_WarnIfError(); #if !CONFIG2_GLES if (!colorAttachment) { glReadBuffer(GL_NONE); glDrawBuffer(GL_NONE); } else glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); #endif ogl_WarnIfError(); #if !CONFIG2_GLES if (framebuffer->m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_FRAMEBUFFER, framebuffer->m_Handle, -1, name); } #else UNUSED2(name); #endif const GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGERROR("CFramebuffer object incomplete: 0x%04X", status); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); return nullptr; } ogl_WarnIfError(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); return framebuffer; } // static std::unique_ptr CFramebuffer::CreateBackbuffer( CDevice* device) { // Backbuffer for GL is a special case with a zero framebuffer. std::unique_ptr framebuffer(new CFramebuffer()); framebuffer->m_Device = device; framebuffer->m_AttachmentMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; CStr skyString = "0 0 0"; CFG_GET_VAL("skycolor", skyString); framebuffer->m_ClearColor.ParseString(skyString, 0.0f); return framebuffer; } CFramebuffer::CFramebuffer() = default; CFramebuffer::~CFramebuffer() { if (m_Handle) glDeleteFramebuffersEXT(1, &m_Handle); } IDevice* CFramebuffer::GetDevice() { return m_Device; } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Texture.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26905) +++ ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26906) @@ -1,309 +1,316 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Texture.h" #include "lib/code_annotation.h" #include "lib/config2.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.h" #include "renderer/backend/gl/Mapping.h" #include namespace Renderer { namespace Backend { namespace GL { namespace { GLint CalculateMinFilter(const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount) { if (MIPLevelCount == 1) return defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST; if (defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR) return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST; return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST; } GLint AddressModeToGLEnum(Sampler::AddressMode addressMode) { switch (addressMode) { case Sampler::AddressMode::REPEAT: return GL_REPEAT; case Sampler::AddressMode::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; case Sampler::AddressMode::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; case Sampler::AddressMode::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; } return GL_REPEAT; } GLenum TypeToGLEnum(CTexture::Type type) { GLenum target = GL_TEXTURE_2D; switch (type) { case CTexture::Type::TEXTURE_2D: target = GL_TEXTURE_2D; break; case CTexture::Type::TEXTURE_2D_MULTISAMPLE: #if CONFIG2_GLES ENSURE(false && "Multisample textures are unsupported on GLES"); #else target = GL_TEXTURE_2D_MULTISAMPLE; #endif break; case CTexture::Type::TEXTURE_CUBE: target = GL_TEXTURE_CUBE_MAP; break; } return target; } } // anonymous namespace // static std::unique_ptr CTexture::Create(CDevice* device, const char* name, const Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { std::unique_ptr texture(new CTexture()); ENSURE(format != Format::UNDEFINED); ENSURE(width > 0 && height > 0 && MIPLevelCount > 0); ENSURE((type == Type::TEXTURE_2D_MULTISAMPLE && sampleCount > 1) || sampleCount == 1); texture->m_Device = device; texture->m_Format = format; texture->m_Type = type; texture->m_Width = width; texture->m_Height = height; texture->m_MIPLevelCount = MIPLevelCount; glGenTextures(1, &texture->m_Handle); ogl_WarnIfError(); const GLenum target = TypeToGLEnum(type); CDeviceCommandContext::ScopedBind scopedBind( texture->m_Device->GetActiveCommandContext(), target, texture->m_Handle); // It's forbidden to set sampler state for multisample textures. if (type != Type::TEXTURE_2D_MULTISAMPLE) { glTexParameteri(target, GL_TEXTURE_MIN_FILTER, CalculateMinFilter(defaultSamplerDesc, MIPLevelCount)); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, defaultSamplerDesc.magFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST); ogl_WarnIfError(); glTexParameteri(target, GL_TEXTURE_WRAP_S, AddressModeToGLEnum(defaultSamplerDesc.addressModeU)); glTexParameteri(target, GL_TEXTURE_WRAP_T, AddressModeToGLEnum(defaultSamplerDesc.addressModeV)); } #if !CONFIG2_GLES if (type == Type::TEXTURE_CUBE) glTexParameteri(target, GL_TEXTURE_WRAP_R, AddressModeToGLEnum(defaultSamplerDesc.addressModeW)); #endif ogl_WarnIfError(); #if !CONFIG2_GLES glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, MIPLevelCount - 1); if (defaultSamplerDesc.mipLODBias != 0.0f) glTexParameteri(target, GL_TEXTURE_LOD_BIAS, defaultSamplerDesc.mipLODBias); #endif // !CONFIG2_GLES if (type == Type::TEXTURE_2D && defaultSamplerDesc.anisotropyEnabled && texture->m_Device->GetCapabilities().anisotropicFiltering) { const float maxAnisotropy = std::min( defaultSamplerDesc.maxAnisotropy, texture->m_Device->GetCapabilities().maxAnisotropy); glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy); } if (defaultSamplerDesc.addressModeU == Sampler::AddressMode::CLAMP_TO_BORDER || defaultSamplerDesc.addressModeV == Sampler::AddressMode::CLAMP_TO_BORDER || defaultSamplerDesc.addressModeW == Sampler::AddressMode::CLAMP_TO_BORDER) { glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, defaultSamplerDesc.borderColor.AsFloatArray().data()); } ogl_WarnIfError(); if (type == CTexture::Type::TEXTURE_2D) { bool compressedFormat = false; GLint internalFormat = GL_RGBA; // Actually pixel data is nullptr so it doesn't make sense to account // it, but in theory some buggy drivers might complain about invalid // pixel format. GLenum pixelFormat = GL_RGBA; GLenum pixelType = GL_UNSIGNED_BYTE; switch (format) { case Format::UNDEFINED: debug_warn("Texture should defined format"); break; case Format::R8G8B8A8_UNORM: break; case Format::R8G8B8_UNORM: internalFormat = GL_RGB; pixelFormat = GL_RGB; pixelType = GL_UNSIGNED_BYTE; break; +#if !CONFIG2_GLES + case Format::R8_UNORM: + internalFormat = GL_RED; + pixelFormat = GL_RED; + pixelType = GL_UNSIGNED_BYTE; + break; +#endif case Format::A8_UNORM: internalFormat = GL_ALPHA; pixelFormat = GL_ALPHA; pixelType = GL_UNSIGNED_BYTE; break; case Format::L8_UNORM: internalFormat = GL_LUMINANCE; pixelFormat = GL_LUMINANCE; pixelType = GL_UNSIGNED_BYTE; break; #if CONFIG2_GLES // GLES requires pixel type == UNSIGNED_SHORT or UNSIGNED_INT for depth. case Format::D16: FALLTHROUGH; case Format::D24: FALLTHROUGH; case Format::D32: internalFormat = GL_DEPTH_COMPONENT; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D24_S8: debug_warn("Unsupported format"); break; #else case Format::D16: internalFormat = GL_DEPTH_COMPONENT16; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D24: internalFormat = GL_DEPTH_COMPONENT24; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D32: internalFormat = GL_DEPTH_COMPONENT32; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D24_S8: internalFormat = GL_DEPTH24_STENCIL8_EXT; pixelFormat = GL_DEPTH_STENCIL_EXT; pixelType = GL_UNSIGNED_INT_24_8_EXT; break; #endif case Format::BC1_RGB_UNORM: FALLTHROUGH; case Format::BC1_RGBA_UNORM: FALLTHROUGH; case Format::BC2_UNORM: FALLTHROUGH; case Format::BC3_UNORM: compressedFormat = true; break; default: debug_warn("Unsupported format."); } // glCompressedTexImage2D can't accept a null data, so we will initialize it during uploading. if (!compressedFormat) { for (uint32_t level = 0; level < MIPLevelCount; ++level) { glTexImage2D(target, level, internalFormat, std::max(1u, width >> level), std::max(1u, height >> level), 0, pixelFormat, pixelType, nullptr); } } } else if (type == CTexture::Type::TEXTURE_2D_MULTISAMPLE) { ENSURE(MIPLevelCount == 1); #if !CONFIG2_GLES if (format == Format::R8G8B8A8_UNORM) { glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_RGBA8, width, height, GL_TRUE); } else if (format == Format::D24_S8) { glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_DEPTH24_STENCIL8_EXT, width, height, GL_TRUE); } else #endif // !CONFIG2_GLES { debug_warn("Unsupported format"); } } #if !CONFIG2_GLES if (format == Format::D16 || format == Format::D24 || format == Format::D32 || format == Format::D24_S8) { if (defaultSamplerDesc.compareEnabled) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, Mapping::FromCompareOp(defaultSamplerDesc.compareOp)); } else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); } #endif ogl_WarnIfError(); if (texture->m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_TEXTURE, texture->m_Handle, -1, name); } return texture; } CTexture::CTexture() = default; CTexture::~CTexture() { m_Device->GetActiveCommandContext()->OnTextureDestroy(this); if (m_Handle) glDeleteTextures(1, &m_Handle); } IDevice* CTexture::GetDevice() { return m_Device; } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Device.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.h (revision 26905) +++ ps/trunk/source/renderer/backend/vulkan/Device.h (revision 26906) @@ -1,103 +1,105 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE #define INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE #include "renderer/backend/IDevice.h" #include "scriptinterface/ScriptForward.h" #include typedef struct SDL_Window SDL_Window; namespace Renderer { namespace Backend { namespace Vulkan { class CDevice : public IDevice { public: /** * Creates the Vulkan device. */ static std::unique_ptr Create(SDL_Window* window); ~CDevice() override; const std::string& GetName() const override { return m_Name; } const std::string& GetVersion() const override { return m_Version; } const std::string& GetDriverInformation() const override { return m_DriverInformation; } const std::vector& GetExtensions() const override { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings) override; IFramebuffer* GetCurrentBackbuffer() override; std::unique_ptr CreateCommandContext() override; std::unique_ptr CreateTexture(const char* name, const ITexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) override; std::unique_ptr CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment) override; std::unique_ptr CreateFramebuffer( const char* name, ITexture* colorAttachment, ITexture* depthStencilAttachment, const CColor& clearColor) override; std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; void Present() override; bool IsTextureFormatSupported(const Format format) const override; + bool IsFramebufferFormatSupported(const Format format) const override; + const Capabilities& GetCapabilities() const override { return m_Capabilities; } private: CDevice(); std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; Capabilities m_Capabilities{}; }; } // namespace Vulkan } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE