From 73fd45558b400883f9287a9f91775598c567327c Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Mon, 20 Feb 2017 14:58:13 -0400 Subject: [PATCH] Implement Array, Cubemap, and Volume texture types (issue #1111). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add love.graphics.newArrayImage, newCubeImage, and newVolumeImage. - Add love.graphics.newCanvas(w, h, layers) and newCanvas(w, h, layers, settings). Add ‘type’ field to the settings table of newCanvas. - Add new love.graphics.setCanvas variants: setCanvas(canvas, slice), and setCanvas(canvastable) where canvastable is in the format: {{canvas1, layer=2}, {canvas2, face=5}} - Add Texture:getTextureType, getDepth, getLayerCount, getMipmapCount, and getFormat. - Remove Image:getData and Image:refresh. - Add Image:replacePixels(imagedata [, slice] [, mipmap]). - Update Canvas:newImageData to accept a slice argument. - Add love.image.newCubeFaces(imagedata). --HG-- branch : minor --- CMakeLists.txt | 6 +- .../xcode/liblove.xcodeproj/project.pbxproj | 30 +- src/common/runtime.cpp | 28 + src/common/runtime.h | 7 + .../glslang/glslang/Include/BaseTypes.h | 4 + .../glslang/glslang/Include/ConstantUnion.h | 8 + .../glslang/glslang/Include/PoolAlloc.h | 2 +- src/libraries/glslang/glslang/Include/Types.h | 155 +++-- .../glslang/glslang/Include/revision.h | 4 +- .../glslang/MachineIndependent/Initialize.cpp | 93 ++- .../MachineIndependent/Intermediate.cpp | 48 +- .../MachineIndependent/ParseContextBase.cpp | 33 +- .../MachineIndependent/ParseHelper.cpp | 43 +- .../glslang/MachineIndependent/ParseHelper.h | 10 +- .../glslang/MachineIndependent/PoolAlloc.cpp | 4 +- .../glslang/MachineIndependent/Scan.cpp | 43 +- .../glslang/MachineIndependent/ShaderLang.cpp | 10 + .../glslang/MachineIndependent/SymbolTable.h | 23 +- .../glslang/MachineIndependent/Versions.cpp | 6 + .../glslang/MachineIndependent/Versions.h | 3 + .../glslang/MachineIndependent/iomapper.cpp | 3 + .../MachineIndependent/localintermediate.h | 4 +- .../MachineIndependent/preprocessor/Pp.cpp | 65 +- .../preprocessor/PpContext.h | 33 +- .../preprocessor/PpScanner.cpp | 2 + .../preprocessor/PpTokens.cpp | 147 +++-- .../preprocessor/PpTokens.h | 6 +- .../glslang/glslang/Public/ShaderLang.h | 1 + src/modules/graphics/Canvas.cpp | 3 +- src/modules/graphics/Canvas.h | 8 +- src/modules/graphics/Font.cpp | 83 ++- src/modules/graphics/Font.h | 30 +- src/modules/graphics/Graphics.cpp | 82 ++- src/modules/graphics/Graphics.h | 55 +- src/modules/graphics/Image.cpp | 176 +++++- src/modules/graphics/Image.h | 65 +- src/modules/graphics/Shader.cpp | 8 + src/modules/graphics/Shader.h | 16 +- src/modules/graphics/Texture.cpp | 115 +++- src/modules/graphics/Texture.h | 57 +- src/modules/graphics/Video.cpp | 56 +- src/modules/graphics/Video.h | 18 +- src/modules/graphics/opengl/Canvas.cpp | 214 +++++-- src/modules/graphics/opengl/Canvas.h | 5 +- src/modules/graphics/opengl/Font.cpp | 190 ------ src/modules/graphics/opengl/Graphics.cpp | 180 +++--- src/modules/graphics/opengl/Graphics.h | 16 +- src/modules/graphics/opengl/Image.cpp | 490 +++++++-------- src/modules/graphics/opengl/Image.h | 41 +- src/modules/graphics/opengl/Mesh.cpp | 8 +- src/modules/graphics/opengl/OpenGL.cpp | 315 ++++++++-- src/modules/graphics/opengl/OpenGL.h | 33 +- .../graphics/opengl/ParticleSystem.cpp | 3 + src/modules/graphics/opengl/Shader.cpp | 283 ++++----- src/modules/graphics/opengl/Shader.h | 19 +- src/modules/graphics/opengl/SpriteBatch.cpp | 19 +- src/modules/graphics/opengl/Text.cpp | 25 +- src/modules/graphics/opengl/Video.cpp | 119 ---- src/modules/graphics/wrap_Canvas.cpp | 70 ++- src/modules/graphics/wrap_Graphics.cpp | 587 +++++++++++++----- src/modules/graphics/wrap_Graphics.lua | 50 +- src/modules/graphics/wrap_Image.cpp | 116 +--- src/modules/graphics/wrap_Image.h | 1 - src/modules/graphics/wrap_Mesh.cpp | 2 +- src/modules/graphics/wrap_ParticleSystem.cpp | 2 +- src/modules/graphics/wrap_Shader.cpp | 7 +- src/modules/graphics/wrap_SpriteBatch.cpp | 2 +- src/modules/graphics/wrap_Texture.cpp | 90 +++ src/modules/image/CompressedImageData.cpp | 89 ++- src/modules/image/CompressedImageData.h | 63 +- src/modules/image/Image.cpp | 87 +++ src/modules/image/Image.h | 6 + src/modules/image/ImageData.cpp | 25 +- src/modules/image/ImageData.h | 37 +- .../opengl/Font.h => image/ImageDataBase.cpp} | 51 +- .../opengl/Video.h => image/ImageDataBase.h} | 35 +- src/modules/image/magpie/ASTCHandler.cpp | 28 +- src/modules/image/magpie/ASTCHandler.h | 7 +- .../image/magpie/CompressedFormatHandler.h | 6 +- .../image/magpie/CompressedImageData.cpp | 30 +- src/modules/image/magpie/ImageData.cpp | 2 +- src/modules/image/magpie/ImageData.h | 2 +- src/modules/image/magpie/KTXHandler.cpp | 31 +- src/modules/image/magpie/KTXHandler.h | 7 +- src/modules/image/magpie/PKMHandler.cpp | 29 +- src/modules/image/magpie/PKMHandler.h | 7 +- src/modules/image/magpie/PVRHandler.cpp | 28 +- src/modules/image/magpie/PVRHandler.h | 7 +- src/modules/image/magpie/ddsHandler.cpp | 88 ++- src/modules/image/magpie/ddsHandler.h | 7 +- src/modules/image/wrap_Image.cpp | 11 + 91 files changed, 3160 insertions(+), 2003 deletions(-) delete mode 100644 src/modules/graphics/opengl/Font.cpp delete mode 100644 src/modules/graphics/opengl/Video.cpp rename src/modules/{graphics/opengl/Font.h => image/ImageDataBase.cpp} (58%) rename src/modules/{graphics/opengl/Video.h => image/ImageDataBase.h} (64%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 342eee33e..aa7ffa4c6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -539,8 +539,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/BufferSync.h src/modules/graphics/opengl/Canvas.cpp src/modules/graphics/opengl/Canvas.h - src/modules/graphics/opengl/Font.cpp - src/modules/graphics/opengl/Font.h src/modules/graphics/opengl/Graphics.cpp src/modules/graphics/opengl/Graphics.h src/modules/graphics/opengl/Image.cpp @@ -559,8 +557,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/StreamBuffer.h src/modules/graphics/opengl/Text.cpp src/modules/graphics/opengl/Text.h - src/modules/graphics/opengl/Video.cpp - src/modules/graphics/opengl/Video.h ) set(LOVE_SRC_MODULE_GRAPHICS @@ -582,6 +578,8 @@ set(LOVE_SRC_MODULE_IMAGE_ROOT src/modules/image/Image.h src/modules/image/ImageData.cpp src/modules/image/ImageData.h + src/modules/image/ImageDataBase.cpp + src/modules/image/ImageDataBase.h src/modules/image/wrap_CompressedImageData.cpp src/modules/image/wrap_CompressedImageData.h src/modules/image/wrap_Image.cpp diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 3fa5fa96b..f3dfd10dd 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -423,9 +423,6 @@ FA0B7D331A95902C000E1D17 /* Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */; }; FA0B7D341A95902C000E1D17 /* Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */; }; FA0B7D351A95902C000E1D17 /* Canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B8E1A95902C000E1D17 /* Canvas.h */; }; - FA0B7D361A95902C000E1D17 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8F1A95902C000E1D17 /* Font.cpp */; }; - FA0B7D371A95902C000E1D17 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8F1A95902C000E1D17 /* Font.cpp */; }; - FA0B7D381A95902C000E1D17 /* Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B901A95902C000E1D17 /* Font.h */; }; FA0B7D391A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B911A95902C000E1D17 /* Graphics.cpp */; }; FA0B7D3A1A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B911A95902C000E1D17 /* Graphics.cpp */; }; FA0B7D3B1A95902C000E1D17 /* Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B921A95902C000E1D17 /* Graphics.h */; }; @@ -860,9 +857,6 @@ FA27B3C01B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */; }; FA27B3C11B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */; }; FA27B3C21B4985BF008A9DCE /* wrap_VideoStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3BA1B4985BF008A9DCE /* wrap_VideoStream.h */; }; - FA27B3C51B4985D8008A9DCE /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3C31B4985D8008A9DCE /* Video.cpp */; }; - FA27B3C61B4985D8008A9DCE /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3C31B4985D8008A9DCE /* Video.cpp */; }; - FA27B3C71B4985D8008A9DCE /* Video.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3C41B4985D8008A9DCE /* Video.h */; }; FA27B3C91B498623008A9DCE /* Theora.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA27B3C81B498623008A9DCE /* Theora.framework */; }; FA28EBD51E352DB5003446F4 /* BufferSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* BufferSync.cpp */; }; FA28EBD61E352DB5003446F4 /* BufferSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* BufferSync.cpp */; }; @@ -996,6 +990,9 @@ FAC756FA1E4F99D200B91289 /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAC756F81E4F99D200B91289 /* Effect.cpp */; }; FAC756FB1E4F99D200B91289 /* Effect.h in Headers */ = {isa = PBXBuildFile; fileRef = FAC756F91E4F99D200B91289 /* Effect.h */; }; FAC756FC1E4F99DB00B91289 /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAC756F81E4F99D200B91289 /* Effect.cpp */; }; + FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; }; + FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; }; + FAD19A191DFF8CA200D5398A /* ImageDataBase.h in Headers */ = {isa = PBXBuildFile; fileRef = FAD19A161DFF8CA200D5398A /* ImageDataBase.h */; }; FADF53F81E3C7ACD00012CC0 /* Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */; }; FADF53F91E3C7ACD00012CC0 /* Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */; }; FADF53FA1E3C7ACD00012CC0 /* Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF53F71E3C7ACD00012CC0 /* Buffer.h */; }; @@ -1456,8 +1453,6 @@ FA0B7B8B1A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = ""; }; FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Canvas.cpp; sourceTree = ""; }; FA0B7B8E1A95902C000E1D17 /* Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Canvas.h; sourceTree = ""; }; - FA0B7B8F1A95902C000E1D17 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = ""; }; - FA0B7B901A95902C000E1D17 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = ""; }; FA0B7B911A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Graphics.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; FA0B7B921A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = ""; }; FA0B7B931A95902C000E1D17 /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = ""; }; @@ -1753,8 +1748,6 @@ FA27B39C1B498151008A9DCE /* wrap_Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Video.h; sourceTree = ""; }; FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_VideoStream.cpp; sourceTree = ""; }; FA27B3BA1B4985BF008A9DCE /* wrap_VideoStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_VideoStream.h; sourceTree = ""; }; - FA27B3C31B4985D8008A9DCE /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; - FA27B3C41B4985D8008A9DCE /* Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Video.h; sourceTree = ""; }; FA27B3C81B498623008A9DCE /* Theora.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Theora.framework; path = /Library/Frameworks/Theora.framework; sourceTree = ""; }; FA283EDC1B27CFAA00C70067 /* nogame.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = nogame.lua; sourceTree = ""; }; FA283EDD1B27CFAA00C70067 /* nogame.lua.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nogame.lua.h; sourceTree = ""; }; @@ -1847,6 +1840,8 @@ FAC756F41E4F99B400B91289 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; FAC756F81E4F99D200B91289 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = ""; }; FAC756F91E4F99D200B91289 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; + FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageDataBase.cpp; sourceTree = ""; }; + FAD19A161DFF8CA200D5398A /* ImageDataBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageDataBase.h; sourceTree = ""; }; FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Buffer.cpp; sourceTree = ""; }; FADF53F71E3C7ACD00012CC0 /* Buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Buffer.h; sourceTree = ""; }; FADF53FB1E3D74F200012CC0 /* Text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Text.cpp; sourceTree = ""; }; @@ -2738,8 +2733,6 @@ FA28EBD41E352DB5003446F4 /* BufferSync.h */, FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */, FA0B7B8E1A95902C000E1D17 /* Canvas.h */, - FA0B7B8F1A95902C000E1D17 /* Font.cpp */, - FA0B7B901A95902C000E1D17 /* Font.h */, FA0B7B911A95902C000E1D17 /* Graphics.cpp */, FA0B7B921A95902C000E1D17 /* Graphics.h */, FA0B7B931A95902C000E1D17 /* Image.cpp */, @@ -2758,8 +2751,6 @@ FA7634491E28722A0066EF9E /* StreamBuffer.h */, FA0B7BA11A95902C000E1D17 /* Text.cpp */, FA0B7BA21A95902C000E1D17 /* Text.h */, - FA27B3C31B4985D8008A9DCE /* Video.cpp */, - FA27B3C41B4985D8008A9DCE /* Video.h */, ); path = opengl; sourceTree = ""; @@ -2773,6 +2764,8 @@ FA0B7BC51A95902C000E1D17 /* Image.h */, FA0B7BC61A95902C000E1D17 /* ImageData.cpp */, FA0B7BC71A95902C000E1D17 /* ImageData.h */, + FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */, + FAD19A161DFF8CA200D5398A /* ImageDataBase.h */, FA0B7BC81A95902C000E1D17 /* magpie */, FA0B7BE21A95902C000E1D17 /* wrap_CompressedImageData.cpp */, FA0B7BE31A95902C000E1D17 /* wrap_CompressedImageData.h */, @@ -3533,7 +3526,6 @@ FAF1405C1E20934C00F898D2 /* InitializeGlobals.h in Headers */, FA0B7DB31A95902C000E1D17 /* wrap_Image.h in Headers */, FA0B7D531A95902C000E1D17 /* Text.h in Headers */, - FA0B7D381A95902C000E1D17 /* Font.h in Headers */, FAF140591E20934C00F898D2 /* Common.h in Headers */, FA0B7EC31A95902C000E1D17 /* threads.h in Headers */, FA0B7AC21A958EA3000E1D17 /* enet.h in Headers */, @@ -3594,7 +3586,6 @@ FA0B7E681A95902C000E1D17 /* wrap_PrismaticJoint.h in Headers */, FA0B7D571A95902C000E1D17 /* Buffer.h in Headers */, FAF140AB1E20934C00F898D2 /* SymbolTable.h in Headers */, - FA27B3C71B4985D8008A9DCE /* Video.h in Headers */, FA0B7ED71A95902D000E1D17 /* Timer.h in Headers */, FA0B7AC31A958EA3000E1D17 /* list.h in Headers */, FA0B7B2D1A958EA3000E1D17 /* core.h in Headers */, @@ -3790,6 +3781,7 @@ FA0B7D841A95902C000E1D17 /* CompressedImageData.h in Headers */, FAF1407E1E20934C00F898D2 /* LiveTraverser.h in Headers */, FA0B7D231A95902C000E1D17 /* Rasterizer.h in Headers */, + FAD19A191DFF8CA200D5398A /* ImageDataBase.h in Headers */, FA0B7CDB1A95902C000E1D17 /* Pool.h in Headers */, FA0B7D0B1A95902C000E1D17 /* wrap_FileData.h in Headers */, FA0B7DF91A95902C000E1D17 /* Body.h in Headers */, @@ -4048,7 +4040,6 @@ FA0B7DB81A95902C000E1D17 /* Joystick.cpp in Sources */, FA0B794B1A958E3B000E1D17 /* wrap_Data.cpp in Sources */, FAB17BEC1ABFAF1800F9BA27 /* CompressedData.cpp in Sources */, - FA0B7D371A95902C000E1D17 /* Font.cpp in Sources */, FA0B7E401A95902C000E1D17 /* wrap_ChainShape.cpp in Sources */, FA0B7DEC1A95902C000E1D17 /* Cursor.cpp in Sources */, FA0B7D871A95902C000E1D17 /* ImageData.cpp in Sources */, @@ -4126,7 +4117,6 @@ FA0B7A7B1A958EA3000E1D17 /* b2Contact.cpp in Sources */, FA0B7A6F1A958EA3000E1D17 /* b2WorldCallbacks.cpp in Sources */, FA0B793C1A958E3B000E1D17 /* runtime.cpp in Sources */, - FA27B3C61B4985D8008A9DCE /* Video.cpp in Sources */, FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */, FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */, FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */, @@ -4194,6 +4184,7 @@ FA0B7A2A1A958EA3000E1D17 /* b2BroadPhase.cpp in Sources */, FA0B7A811A958EA3000E1D17 /* b2EdgeAndCircleContact.cpp in Sources */, FA0B7D071A95902C000E1D17 /* wrap_File.cpp in Sources */, + FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */, FA0B7AD01A958EA3000E1D17 /* peer.c in Sources */, FA27B3C11B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */, FADF54211E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */, @@ -4409,7 +4400,6 @@ FA0B7DB71A95902C000E1D17 /* Joystick.cpp in Sources */, FA0B7A321A958EA3000E1D17 /* b2Collision.cpp in Sources */, FAB17BEB1ABFAF1800F9BA27 /* CompressedData.cpp in Sources */, - FA0B7D361A95902C000E1D17 /* Font.cpp in Sources */, FA0B7E3F1A95902C000E1D17 /* wrap_ChainShape.cpp in Sources */, FA0B7DEB1A95902C000E1D17 /* Cursor.cpp in Sources */, FA0B7D861A95902C000E1D17 /* ImageData.cpp in Sources */, @@ -4486,7 +4476,6 @@ FAF140AC1E20934C00F898D2 /* Versions.cpp in Sources */, FA0B7A441A958EA3000E1D17 /* b2EdgeShape.cpp in Sources */, FA0B79461A958E3B000E1D17 /* Vector.cpp in Sources */, - FA27B3C51B4985D8008A9DCE /* Video.cpp in Sources */, FA0B7DBB1A95902C000E1D17 /* Joystick.cpp in Sources */, FAF140DB1E20934C00F898D2 /* InitializeDll.cpp in Sources */, FA0B7DAE1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */, @@ -4555,6 +4544,7 @@ FA0B7A951A958EA3000E1D17 /* b2Joint.cpp in Sources */, FA0B7D061A95902C000E1D17 /* wrap_File.cpp in Sources */, FA0B7A4E1A958EA3000E1D17 /* b2Draw.cpp in Sources */, + FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */, FA27B3C01B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */, FA0B7D931A95902C000E1D17 /* Image.cpp in Sources */, FADF54201E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */, diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index 215146cb9..b843e8fcb 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -130,6 +130,17 @@ void luax_printstack(lua_State *L) std::cout << i << " - " << luaL_typename(L, i) << std::endl; } +bool luax_isarrayoftables(lua_State *L, int idx) +{ + if (!lua_istable(L, idx)) + return false; + + lua_rawgeti(L, idx, 1); + bool tableoftables = lua_istable(L, -1); + lua_pop(L, 1); + return tableoftables; +} + bool luax_toboolean(lua_State *L, int idx) { return (lua_toboolean(L, idx) != 0); @@ -208,6 +219,23 @@ double luax_numberflag(lua_State *L, int table_index, const char *key, double de return retval; } +int luax_checkintflag(lua_State *L, int table_index, const char *key) +{ + lua_getfield(L, table_index, key); + + int retval; + if (!lua_isnumber(L, -1)) + { + std::string err = "expected integer field " + std::string(key) + " in table"; + return luaL_argerror(L, table_index, err.c_str()); + } + else + retval = (int) luaL_checkinteger(L, -1); + lua_pop(L, 1); + + return retval; +} + int luax_assert_argc(lua_State *L, int min) { int argc = lua_gettop(L); diff --git a/src/common/runtime.h b/src/common/runtime.h index 5d60cabb6..7082530fb 100644 --- a/src/common/runtime.h +++ b/src/common/runtime.h @@ -106,6 +106,11 @@ Reference *luax_refif(lua_State *L, int type); **/ void luax_printstack(lua_State *L); +/** + * Gets whether the value at idx is an array of tables. + **/ +bool luax_isarrayoftables(lua_State *L, int idx); + /** * Converts the value at idx to a bool. It follow the same rules * as lua_toboolean, but returns a bool instead of an int. @@ -163,6 +168,8 @@ bool luax_boolflag(lua_State *L, int table_index, const char *key, bool defaultV int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValue); double luax_numberflag(lua_State *L, int table_index, const char *key, double defaultValue); +int luax_checkintflag(lua_State *L, int table_index, const char *key); + /** * Convert the value at the specified index to an Lua number, and then * convert to a float. diff --git a/src/libraries/glslang/glslang/Include/BaseTypes.h b/src/libraries/glslang/glslang/Include/BaseTypes.h index 449e3aeac..f0e3c3781 100755 --- a/src/libraries/glslang/glslang/Include/BaseTypes.h +++ b/src/libraries/glslang/glslang/Include/BaseTypes.h @@ -207,6 +207,8 @@ enum TBuiltInVariable { EbvViewportMaskNV, EbvSecondaryPositionNV, EbvSecondaryViewportMaskNV, + EbvPositionPerViewNV, + EbvViewportMaskPerViewNV, #endif // HLSL built-ins that live only temporarily, until they get remapped // to one of the above. @@ -325,6 +327,8 @@ __inline const char* GetBuiltInVariableString(TBuiltInVariable v) case EbvViewportMaskNV: return "ViewportMaskNV"; case EbvSecondaryPositionNV: return "SecondaryPositionNV"; case EbvSecondaryViewportMaskNV: return "SecondaryViewportMaskNV"; + case EbvPositionPerViewNV: return "PositionPerViewNV"; + case EbvViewportMaskPerViewNV: return "ViewportMaskPerViewNV"; #endif default: return "unknown built-in variable"; } diff --git a/src/libraries/glslang/glslang/Include/ConstantUnion.h b/src/libraries/glslang/glslang/Include/ConstantUnion.h index b95bc25a0..f66a7ff51 100755 --- a/src/libraries/glslang/glslang/Include/ConstantUnion.h +++ b/src/libraries/glslang/glslang/Include/ConstantUnion.h @@ -81,12 +81,19 @@ public: type = EbtBool; } + void setSConst(const TString* s) + { + sConst = s; + type = EbtString; + } + int getIConst() const { return iConst; } unsigned int getUConst() const { return uConst; } long long getI64Const() const { return i64Const; } unsigned long long getU64Const() const { return u64Const; } double getDConst() const { return dConst; } bool getBConst() const { return bConst; } + const TString* getSConst() const { return sConst; } bool operator==(const int i) const { @@ -532,6 +539,7 @@ private: unsigned long long u64Const; // used for u64vec, scalar uint64s bool bConst; // used for bvec, scalar bools double dConst; // used for vec, dvec, mat, dmat, scalar floats and doubles + const TString* sConst; // string constant }; TBasicType type; diff --git a/src/libraries/glslang/glslang/Include/PoolAlloc.h b/src/libraries/glslang/glslang/Include/PoolAlloc.h index 100c7d0b5..69bacb156 100755 --- a/src/libraries/glslang/glslang/Include/PoolAlloc.h +++ b/src/libraries/glslang/glslang/Include/PoolAlloc.h @@ -255,7 +255,7 @@ extern TPoolAllocator& GetThreadPoolAllocator(); struct TThreadMemoryPools { - TPoolAllocator* threadPoolAllocator; + TPoolAllocator* threadPoolAllocator; }; void SetThreadPoolAllocator(TPoolAllocator& poolAllocator); diff --git a/src/libraries/glslang/glslang/Include/Types.h b/src/libraries/glslang/glslang/Include/Types.h index 6fce15ec3..4a14843dc 100755 --- a/src/libraries/glslang/glslang/Include/Types.h +++ b/src/libraries/glslang/glslang/Include/Types.h @@ -401,8 +401,23 @@ public: // drop qualifiers that don't belong in a temporary variable void makeTemporary() { - storage = EvqTemporary; - builtIn = EbvNone; + storage = EvqTemporary; + builtIn = EbvNone; + clearInterstage(); + clearMemory(); + specConstant = false; + clearLayout(); + } + + void clearInterstage() + { + clearInterpolation(); + patch = false; + sample = false; + } + + void clearInterpolation() + { centroid = false; smooth = false; flat = false; @@ -410,15 +425,15 @@ public: #ifdef AMD_EXTENSIONS explicitInterp = false; #endif - patch = false; - sample = false; + } + + void clearMemory() + { coherent = false; volatil = false; restrict = false; readonly = false; writeonly = false; - specConstant = false; - clearLayout(); } // Drop just the storage qualification, which perhaps should @@ -575,42 +590,47 @@ public: } // Implementing an embedded layout-qualifier class here, since C++ can't have a real class bitfield - void clearLayout() + void clearLayout() // all layout { - layoutMatrix = ElmNone; - layoutPacking = ElpNone; - layoutOffset = layoutNotSet; - layoutAlign = layoutNotSet; - - layoutLocation = layoutLocationEnd; - layoutComponent = layoutComponentEnd; - layoutSet = layoutSetEnd; - layoutBinding = layoutBindingEnd; - layoutIndex = layoutIndexEnd; - - layoutStream = layoutStreamEnd; - - layoutXfbBuffer = layoutXfbBufferEnd; - layoutXfbStride = layoutXfbStrideEnd; - layoutXfbOffset = layoutXfbOffsetEnd; - layoutAttachment = layoutAttachmentEnd; - layoutSpecConstantId = layoutSpecConstantIdEnd; - - layoutFormat = ElfNone; + clearUniformLayout(); layoutPushConstant = false; #ifdef NV_EXTENSIONS layoutPassthrough = false; layoutViewportRelative = false; - // -2048 as the default vaule indicating layoutSecondaryViewportRelative is not set + // -2048 as the default value indicating layoutSecondaryViewportRelative is not set layoutSecondaryViewportRelativeOffset = -2048; #endif + + clearInterstageLayout(); + + layoutSpecConstantId = layoutSpecConstantIdEnd; + + layoutFormat = ElfNone; } + void clearInterstageLayout() + { + layoutLocation = layoutLocationEnd; + layoutComponent = layoutComponentEnd; + layoutIndex = layoutIndexEnd; + clearStreamLayout(); + clearXfbLayout(); + } + void clearStreamLayout() + { + layoutStream = layoutStreamEnd; + } + void clearXfbLayout() + { + layoutXfbBuffer = layoutXfbBufferEnd; + layoutXfbStride = layoutXfbStrideEnd; + layoutXfbOffset = layoutXfbOffsetEnd; + } + bool hasLayout() const { return hasUniformLayout() || hasAnyLocation() || - hasBinding() || hasStream() || hasXfb() || hasFormat() || @@ -670,8 +690,21 @@ public: hasPacking() || hasOffset() || hasBinding() || + hasSet() || hasAlign(); } + void clearUniformLayout() // only uniform specific + { + layoutMatrix = ElmNone; + layoutPacking = ElpNone; + layoutOffset = layoutNotSet; + layoutAlign = layoutNotSet; + + layoutSet = layoutSetEnd; + layoutBinding = layoutBindingEnd; + layoutAttachment = layoutAttachmentEnd; + } + bool hasMatrix() const { return layoutMatrix != ElmNone; @@ -1202,30 +1235,11 @@ public: typeName = copyOf.typeName; } + // Make complete copy of the whole type graph rooted at 'copyOf'. void deepCopy(const TType& copyOf) { - shallowCopy(copyOf); - - if (copyOf.arraySizes) { - arraySizes = new TArraySizes; - *arraySizes = *copyOf.arraySizes; - } - - if (copyOf.structure) { - structure = new TTypeList; - for (unsigned int i = 0; i < copyOf.structure->size(); ++i) { - TTypeLoc typeLoc; - typeLoc.loc = (*copyOf.structure)[i].loc; - typeLoc.type = new TType(); - typeLoc.type->deepCopy(*(*copyOf.structure)[i].type); - structure->push_back(typeLoc); - } - } - - if (copyOf.fieldName) - fieldName = NewPoolTString(copyOf.fieldName->c_str()); - if (copyOf.typeName) - typeName = NewPoolTString(copyOf.typeName->c_str()); + TMap copied; // to enable copying a type graph as a graph, not a tree + deepCopy(copyOf, copied); } // Recursively make temporary @@ -1346,6 +1360,8 @@ public: case EbvViewportMaskNV: case EbvSecondaryPositionNV: case EbvSecondaryViewportMaskNV: + case EbvPositionPerViewNV: + case EbvViewportMaskPerViewNV: #endif return true; default: @@ -1720,6 +1736,7 @@ public: const char* getBuiltInVariableString() const { return GetBuiltInVariableString(qualifier.builtIn); } const char* getPrecisionQualifierString() const { return GetPrecisionQualifierString(qualifier.precision); } const TTypeList* getStruct() const { return structure; } + void setStruct(TTypeList* s) { structure = s; } TTypeList* getWritableStruct() const { return structure; } // This should only be used when known to not be sharing with other threads int computeNumComponents() const @@ -1830,6 +1847,42 @@ protected: TType(const TType& type); TType& operator=(const TType& type); + // Recursively copy a type graph, while preserving the graph-like + // quality. That is, don't make more than one copy of a structure that + // gets reused multiple times in the type graph. + void deepCopy(const TType& copyOf, TMap& copiedMap) + { + shallowCopy(copyOf); + + if (copyOf.arraySizes) { + arraySizes = new TArraySizes; + *arraySizes = *copyOf.arraySizes; + } + + if (copyOf.structure) { + auto prevCopy = copiedMap.find(copyOf.structure); + if (prevCopy != copiedMap.end()) + structure = prevCopy->second; + else { + structure = new TTypeList; + copiedMap[copyOf.structure] = structure; + for (unsigned int i = 0; i < copyOf.structure->size(); ++i) { + TTypeLoc typeLoc; + typeLoc.loc = (*copyOf.structure)[i].loc; + typeLoc.type = new TType(); + typeLoc.type->deepCopy(*(*copyOf.structure)[i].type, copiedMap); + structure->push_back(typeLoc); + } + } + } + + if (copyOf.fieldName) + fieldName = NewPoolTString(copyOf.fieldName->c_str()); + if (copyOf.typeName) + typeName = NewPoolTString(copyOf.typeName->c_str()); + } + + void buildMangledName(TString&); TBasicType basicType : 8; diff --git a/src/libraries/glslang/glslang/Include/revision.h b/src/libraries/glslang/glslang/Include/revision.h index 5ef393d39..50deb6445 100755 --- a/src/libraries/glslang/glslang/Include/revision.h +++ b/src/libraries/glslang/glslang/Include/revision.h @@ -2,5 +2,5 @@ // For the version, it uses the latest git tag followed by the number of commits. // For the date, it uses the current date (when then script is run). -#define GLSLANG_REVISION "Overload400-PrecQual.1773" -#define GLSLANG_DATE "19-Jan-2017" +#define GLSLANG_REVISION "Overload400-PrecQual.1843" +#define GLSLANG_DATE "18-Feb-2017" diff --git a/src/libraries/glslang/glslang/MachineIndependent/Initialize.cpp b/src/libraries/glslang/glslang/MachineIndependent/Initialize.cpp index 637ab29d4..05f698028 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/Initialize.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/Initialize.cpp @@ -1292,6 +1292,8 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "vec4 textureCube(samplerCube, vec3);" + "vec4 texture2DArray(sampler2DArray, vec3);" // GL_EXT_texture_array + "\n"); } } @@ -1317,6 +1319,10 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "vec4 shadow2DRect(sampler2DRectShadow, vec3);" // GL_ARB_texture_rectangle, caught by keyword check "vec4 shadow2DRectProj(sampler2DRectShadow, vec4);" // GL_ARB_texture_rectangle, caught by keyword check + "vec4 texture1DArray(sampler1DArray, vec2);" // GL_EXT_texture_array + "vec4 shadow1DArray(sampler1DArrayShadow, vec3);" // GL_EXT_texture_array + "vec4 shadow2DArray(sampler2DArrayShadow, vec4);" // GL_EXT_texture_array + "\n"); } } @@ -2646,6 +2652,8 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "vec4 texture3DProjLod(sampler3D, vec4, float);" // GL_ARB_shader_texture_lod // OES_texture_3D, but caught by keyword check "vec4 textureCubeLod(samplerCube, vec3, float);" // GL_ARB_shader_texture_lod + "vec4 texture2DArrayLod(sampler2DArray, vec3, float);" // GL_EXT_texture_array + "\n"); } } @@ -2681,6 +2689,10 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "vec4 shadow2DRectGradARB( sampler2DRectShadow, vec3, vec2, vec2);" // GL_ARB_shader_texture_lod "vec4 shadow2DRectProjGradARB(sampler2DRectShadow, vec4, vec2, vec2);" // GL_ARB_shader_texture_lod + "vec4 texture1DArrayLod(sampler1DArray, vec2, float);" // GL_EXT_texture_array + "vec4 shadow1DArrayLod(sampler1DArrayShadow, vec3, float);" // GL_EXT_texture_array + "vec4 shadow2DArrayLod(sampler2DArrayShadow, vec4, float);" // GL_EXT_texture_array + "\n"); } } @@ -2752,6 +2764,7 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "vec4 texture3D(sampler3D, vec3, float);" // OES_texture_3D "vec4 texture3DProj(sampler3D, vec4, float);" // OES_texture_3D "vec4 textureCube(samplerCube, vec3, float);" + "vec4 texture2DArray(sampler2DArray, vec3, float);" // GL_EXT_texture_array "\n"); } @@ -2764,7 +2777,9 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "vec4 shadow2D(sampler2DShadow, vec3, float);" "vec4 shadow1DProj(sampler1DShadow, vec4, float);" "vec4 shadow2DProj(sampler2DShadow, vec4, float);" - + "vec4 texture1DArray(sampler1DArray, vec2, float);" // GL_EXT_texture_array + "vec4 shadow1DArray(sampler1DArrayShadow, vec3, float);" // GL_EXT_texture_array + "vec4 shadow2DArray(sampler2DArrayShadow, vec4, float);" // GL_EXT_texture_array "\n"); } if (spvVersion.spv == 0 && profile == EEsProfile) { @@ -3249,6 +3264,8 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "out int gl_ViewportMask[];" "out int gl_SecondaryViewportMaskNV[];" "out vec4 gl_SecondaryPositionNV;" + "out vec4 gl_PositionPerViewNV[];" + "out int gl_ViewportMaskPerViewNV[];" ); #endif @@ -3313,6 +3330,7 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV "float gl_CullDistance[];" #ifdef NV_EXTENSIONS "vec4 gl_SecondaryPositionNV;" + "vec4 gl_PositionPerViewNV[];" #endif ); stageBuiltins[EShLangGeometry].append( @@ -3362,9 +3380,11 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV #ifdef NV_EXTENSIONS if (version >= 450) stageBuiltins[EShLangGeometry].append( - "out int gl_ViewportMask[];" - "out int gl_SecondaryViewportMaskNV[];" - "out vec4 gl_SecondaryPositionNV;" + "out int gl_ViewportMask[];" + "out int gl_SecondaryViewportMaskNV[];" + "out vec4 gl_SecondaryPositionNV;" + "out vec4 gl_PositionPerViewNV[];" + "out int gl_ViewportMaskPerViewNV[];" ); #endif @@ -3424,11 +3444,13 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV stageBuiltins[EShLangTessControl].append( "float gl_CullDistance[];" #ifdef NV_EXTENSIONS - "int gl_ViewportIndex;" - "int gl_Layer;" - "int gl_ViewportMask[];" + "int gl_ViewportIndex;" + "int gl_Layer;" + "int gl_ViewportMask[];" "vec4 gl_SecondaryPositionNV;" - "int gl_SecondaryViewportMaskNV[];" + "int gl_SecondaryViewportMaskNV[];" + "vec4 gl_PositionPerViewNV[];" + "int gl_ViewportMaskPerViewNV[];" #endif ); stageBuiltins[EShLangTessControl].append( @@ -3503,11 +3525,13 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV #ifdef NV_EXTENSIONS if (version >= 450) stageBuiltins[EShLangTessEvaluation].append( - "out int gl_ViewportIndex;" - "out int gl_Layer;" - "out int gl_ViewportMask[];" + "out int gl_ViewportIndex;" + "out int gl_Layer;" + "out int gl_ViewportMask[];" "out vec4 gl_SecondaryPositionNV;" - "out int gl_SecondaryViewportMaskNV[];" + "out int gl_SecondaryViewportMaskNV[];" + "out vec4 gl_PositionPerViewNV[];" + "out int gl_ViewportMaskPerViewNV[];" ); #endif @@ -4203,7 +4227,7 @@ void TBuiltIns::addSamplingFunctions(TSampler sampler, TString& typeName, int ve // Add to the per-language set of built-ins - if (bias) + if (bias || lodClamp) stageBuiltins[EShLangFragment].append(s); else commonBuiltins.append(s); @@ -4446,6 +4470,7 @@ void TBuiltIns::initialize(const TBuiltInResource &resources, int version, EProf "highp float gl_PointSize;" #ifdef NV_EXTENSIONS "highp vec4 gl_SecondaryPositionNV;" + "highp vec4 gl_PositionPerViewNV[];" #endif "} gl_in[gl_MaxPatchVertices];" "\n"); @@ -4635,6 +4660,7 @@ void TBuiltIns::initialize(const TBuiltInResource &resources, int version, EProf "float gl_CullDistance[];" #ifdef NV_EXTENSIONS "vec4 gl_SecondaryPositionNV;" + "vec4 gl_PositionPerViewNV[];" #endif ); s.append( @@ -5033,19 +5059,26 @@ void TBuiltIns::identifyBuiltIns(int version, EProfile profile, const SpvVersion symbolTable.setVariableExtensions("gl_ViewportMask", 1, &E_GL_NV_viewport_array2); symbolTable.setVariableExtensions("gl_SecondaryPositionNV", 1, &E_GL_NV_stereo_view_rendering); symbolTable.setVariableExtensions("gl_SecondaryViewportMaskNV", 1, &E_GL_NV_stereo_view_rendering); + symbolTable.setVariableExtensions("gl_PositionPerViewNV", 1, &E_GL_NVX_multiview_per_view_attributes); + symbolTable.setVariableExtensions("gl_ViewportMaskPerViewNV", 1, &E_GL_NVX_multiview_per_view_attributes); BuiltInVariable("gl_ViewportMask", EbvViewportMaskNV, symbolTable); BuiltInVariable("gl_SecondaryPositionNV", EbvSecondaryPositionNV, symbolTable); BuiltInVariable("gl_SecondaryViewportMaskNV", EbvSecondaryViewportMaskNV, symbolTable); + BuiltInVariable("gl_PositionPerViewNV", EbvPositionPerViewNV, symbolTable); + BuiltInVariable("gl_ViewportMaskPerViewNV", EbvViewportMaskPerViewNV, symbolTable); - if (language != EShLangVertex) + if (language != EShLangVertex) { BuiltInVariable("gl_in", "gl_SecondaryPositionNV", EbvSecondaryPositionNV, symbolTable); - + BuiltInVariable("gl_in", "gl_PositionPerViewNV", EbvPositionPerViewNV, symbolTable); + } BuiltInVariable("gl_out", "gl_Layer", EbvLayer, symbolTable); BuiltInVariable("gl_out", "gl_ViewportIndex", EbvViewportIndex, symbolTable); BuiltInVariable("gl_out", "gl_ViewportMask", EbvViewportMaskNV, symbolTable); BuiltInVariable("gl_out", "gl_SecondaryPositionNV", EbvSecondaryPositionNV, symbolTable); BuiltInVariable("gl_out", "gl_SecondaryViewportMaskNV", EbvSecondaryViewportMaskNV, symbolTable); + BuiltInVariable("gl_out", "gl_PositionPerViewNV", EbvPositionPerViewNV, symbolTable); + BuiltInVariable("gl_out", "gl_ViewportMaskPerViewNV", EbvViewportMaskPerViewNV, symbolTable); #endif BuiltInVariable("gl_PatchVerticesIn", EbvPatchVertices, symbolTable); @@ -5197,6 +5230,27 @@ void TBuiltIns::identifyBuiltIns(int version, EProfile profile, const SpvVersion symbolTable.setFunctionExtensions("shadow2DRectProjGradARB", 1, &E_GL_ARB_shader_texture_lod); } + // E_GL_EXT_texture_array + if (spvVersion.spv == 0) { + symbolTable.setFunctionExtensions("texture2DArray", 1, &E_GL_EXT_texture_array); + } + if (profile != EEsProfile && spvVersion.spv == 0) { + int nLodExtensions = 2; + const char *lodExtensions[] = {E_GL_EXT_texture_array, E_GL_ARB_shader_texture_lod}; + + if (version >= 130) + nLodExtensions = 1; + + symbolTable.setFunctionExtensions("texture1DArray", 1, &E_GL_EXT_texture_array); + symbolTable.setFunctionExtensions("shadow1DArray", 1, &E_GL_EXT_texture_array); + symbolTable.setFunctionExtensions("shadow2DArray", 1, &E_GL_EXT_texture_array); + + symbolTable.setFunctionExtensions("texture1DArrayLod", nLodExtensions, lodExtensions); + symbolTable.setFunctionExtensions("texture2DArrayLod", nLodExtensions, lodExtensions); + symbolTable.setFunctionExtensions("shadow1DArrayLod", nLodExtensions, lodExtensions); + symbolTable.setFunctionExtensions("shadow2DArrayLod", nLodExtensions, lodExtensions); + } + // E_GL_ARB_shader_image_load_store if (profile != EEsProfile && version < 420) symbolTable.setFunctionExtensions("memoryBarrier", 1, &E_GL_ARB_shader_image_load_store); @@ -5555,6 +5609,15 @@ void TBuiltIns::identifyBuiltIns(int version, EProfile profile, const SpvVersion symbolTable.relateToOperator("texture2DProjLod", EOpTextureProjLod); symbolTable.relateToOperator("texture2DProjLodEXT", EOpTextureProjLod); + symbolTable.relateToOperator("texture1DArray", EOpTexture); + symbolTable.relateToOperator("texture2DArray", EOpTexture); + symbolTable.relateToOperator("shadow1DArray", EOpTexture); + symbolTable.relateToOperator("shadow2DArray", EOpTexture); + symbolTable.relateToOperator("texture1DArrayLod", EOpTextureLod); + symbolTable.relateToOperator("texture2DArrayLod", EOpTextureLod); + symbolTable.relateToOperator("shadow1DArrayLod", EOpTextureLod); + symbolTable.relateToOperator("shadow2DArrayLod", EOpTextureLod); + symbolTable.relateToOperator("texture3D", EOpTexture); symbolTable.relateToOperator("texture3DGradARB", EOpTextureGrad); symbolTable.relateToOperator("texture3DProj", EOpTextureProj); diff --git a/src/libraries/glslang/glslang/MachineIndependent/Intermediate.cpp b/src/libraries/glslang/glslang/MachineIndependent/Intermediate.cpp index 8d68517a3..2ebd741ee 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/Intermediate.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/Intermediate.cpp @@ -155,14 +155,10 @@ TIntermTyped* TIntermediate::addBinaryMath(TOperator op, TIntermTyped* left, TIn return folded; } - // If either is a specialization constant, while the other is - // a constant (or specialization constant), the result is still - // a specialization constant, if the operation is an allowed - // specialization-constant operation. - if (( left->getType().getQualifier().isSpecConstant() && right->getType().getQualifier().isConstant()) || - (right->getType().getQualifier().isSpecConstant() && left->getType().getQualifier().isConstant())) - if (isSpecializationOperation(*node)) - node->getWritableType().getQualifier().makeSpecConstant(); + // If can propagate spec-constantness and if the operation is an allowed + // specialization-constant operation, make a spec-constant. + if (specConstantPropagates(*left, *right) && isSpecializationOperation(*node)) + node->getWritableType().getQualifier().makeSpecConstant(); return node; } @@ -1232,7 +1228,7 @@ TIntermAggregate* TIntermediate::makeAggregate(const TSourceLoc& loc) // // Returns the selection node created. // -TIntermNode* TIntermediate::addSelection(TIntermTyped* cond, TIntermNodePair nodePair, const TSourceLoc& loc) +TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermNodePair nodePair, const TSourceLoc& loc) { // // Don't prune the false path for compile-time constants; it's needed @@ -1277,10 +1273,19 @@ TIntermTyped* TIntermediate::addMethod(TIntermTyped* object, const TType& type, // a true path, and a false path. The two paths are specified // as separate parameters. // +// Specialization constant operations include +// - The ternary operator ( ? : ) +// // Returns the selection node created, or nullptr if one could not be. // TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermTyped* trueBlock, TIntermTyped* falseBlock, const TSourceLoc& loc) { + // If it's void, go to the if-then-else selection() + if (trueBlock->getBasicType() == EbtVoid && falseBlock->getBasicType() == EbtVoid) { + TIntermNodePair pair = { trueBlock, falseBlock }; + return addSelection(cond, pair, loc); + } + // // Get compatible types. // @@ -1314,10 +1319,16 @@ TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermTyped* true // Make a selection node. // TIntermSelection* node = new TIntermSelection(cond, trueBlock, falseBlock, trueBlock->getType()); - node->getQualifier().makeTemporary(); node->setLoc(loc); node->getQualifier().precision = std::max(trueBlock->getQualifier().precision, falseBlock->getQualifier().precision); + if ((cond->getQualifier().isConstant() && specConstantPropagates(*trueBlock, *falseBlock)) || + (cond->getQualifier().isSpecConstant() && trueBlock->getQualifier().isConstant() && + falseBlock->getQualifier().isConstant())) + node->getQualifier().makeSpecConstant(); + else + node->getQualifier().makeTemporary(); + return node; } @@ -1392,6 +1403,14 @@ TIntermConstantUnion* TIntermediate::addConstantUnion(double d, TBasicType baseT return addConstantUnion(unionArray, TType(baseType, EvqConst), loc, literal); } +TIntermConstantUnion* TIntermediate::addConstantUnion(const TString* s, const TSourceLoc& loc, bool literal) const +{ + TConstUnionArray unionArray(1); + unionArray[0].setSConst(s); + + return addConstantUnion(unionArray, TType(EbtString, EvqConst), loc, literal); +} + // Put vector swizzle selectors onto the given sequence void TIntermediate::pushSelector(TIntermSequence& sequence, const TVectorSelector& selector, const TSourceLoc& loc) { @@ -2639,4 +2658,13 @@ void TIntermAggregate::addToPragmaTable(const TPragmaTable& pTable) *pragmaTable = pTable; } +// If either node is a specialization constant, while the other is +// a constant (or specialization constant), the result is still +// a specialization constant. +bool TIntermediate::specConstantPropagates(const TIntermTyped& node1, const TIntermTyped& node2) +{ + return (node1.getType().getQualifier().isSpecConstant() && node2.getType().getQualifier().isConstant()) || + (node2.getType().getQualifier().isSpecConstant() && node1.getType().getQualifier().isConstant()); +} + } // end namespace glslang diff --git a/src/libraries/glslang/glslang/MachineIndependent/ParseContextBase.cpp b/src/libraries/glslang/glslang/MachineIndependent/ParseContextBase.cpp index 24c8e6095..946eb715b 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/ParseContextBase.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/ParseContextBase.cpp @@ -223,20 +223,11 @@ void TParseContextBase::rValueErrorCheck(const TSourceLoc& loc, const char* op, error(loc, "can't read from writeonly object: ", op, symNode->getName().c_str()); } -// Add a linkage symbol node for 'symbol', which -// must have its type fully edited, as this will snapshot the type. -// It is okay if symbol becomes invalid before finish(). -void TParseContextBase::trackLinkage(TSymbol& symbol) -{ - if (!parsingBuiltins) - intermediate.addSymbolLinkageNode(linkage, symbol); -} - // Add 'symbol' to the list of deferred linkage symbols, which // are later processed in finish(), at which point the symbol // must still be valid. // It is okay if the symbol's type will be subsequently edited. -void TParseContextBase::trackLinkageDeferred(TSymbol& symbol) +void TParseContextBase::trackLinkage(TSymbol& symbol) { if (!parsingBuiltins) linkageSymbols.push_back(&symbol); @@ -253,7 +244,7 @@ void TParseContextBase::makeEditable(TSymbol*& symbol) // Save it (deferred, so it can be edited first) in the AST for linker use. if (symbol) - trackLinkageDeferred(*symbol); + trackLinkage(*symbol); } // Return a writable version of the variable 'name'. @@ -539,7 +530,7 @@ void TParseContextBase::parseSwizzleSelector(const TSourceLoc& loc, const TStrin // Make the passed-in variable information become a member of the // global uniform block. If this doesn't exist yet, make it. // -void TParseContextBase::growGlobalUniformBlock(TSourceLoc& loc, TType& memberType, TString& memberName) +void TParseContextBase::growGlobalUniformBlock(TSourceLoc& loc, TType& memberType, TString& memberName, TTypeList* typeList) { // make the global block, if not yet made if (globalUniformBlock == nullptr) { @@ -557,6 +548,8 @@ void TParseContextBase::growGlobalUniformBlock(TSourceLoc& loc, TType& memberTyp TType* type = new TType; type->shallowCopy(memberType); type->setFieldName(memberName); + if (typeList) + type->setStruct(typeList); TTypeLoc typeLoc = {type, loc}; globalUniformBlock->getType().getWritableStruct()->push_back(typeLoc); } @@ -577,7 +570,7 @@ bool TParseContextBase::insertGlobalUniformBlock() // This is the first request; we need a normal symbol table insert inserted = symbolTable.insert(*globalUniformBlock); if (inserted) - trackLinkageDeferred(*globalUniformBlock); + trackLinkage(*globalUniformBlock); } else if (firstNewMember <= numMembers) { // This is a follow-on request; we need to amend the first insert inserted = symbolTable.amend(*globalUniformBlock, firstNewMember); @@ -593,12 +586,14 @@ bool TParseContextBase::insertGlobalUniformBlock() void TParseContextBase::finish() { - if (!parsingBuiltins) { - // Transfer the linkage symbols to AST nodes - for (auto i = linkageSymbols.begin(); i != linkageSymbols.end(); ++i) - intermediate.addSymbolLinkageNode(linkage, **i); - intermediate.addSymbolLinkageNodes(linkage, getLanguage(), symbolTable); - } + if (parsingBuiltins) + return; + + // Transfer the linkage symbols to AST nodes + TIntermAggregate* linkage = new TIntermAggregate; + for (auto i = linkageSymbols.begin(); i != linkageSymbols.end(); ++i) + intermediate.addSymbolLinkageNode(linkage, **i); + intermediate.addSymbolLinkageNodes(linkage, getLanguage(), symbolTable); } } // end namespace glslang diff --git a/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.cpp b/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.cpp index f5059258c..db77b41d5 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.cpp @@ -3009,7 +3009,7 @@ void TParseContext::declareArray(const TSourceLoc& loc, TString& identifier, con symbol = new TVariable(&identifier, type); symbolTable.insert(*symbol); if (symbolTable.atGlobalLevel()) - trackLinkageDeferred(*symbol); + trackLinkage(*symbol); if (! symbolTable.atBuiltInLevel()) { if (isIoResizeArray(type)) { @@ -3431,9 +3431,9 @@ void TParseContext::redeclareBuiltinBlock(const TSourceLoc& loc, TTypeList& newT oldType.getQualifier().layoutViewportRelative = newType.getQualifier().layoutViewportRelative; oldType.getQualifier().layoutSecondaryViewportRelativeOffset = newType.getQualifier().layoutSecondaryViewportRelativeOffset; } +#endif if (oldType.isImplicitlySizedArray() && newType.isExplicitlySizedArray()) oldType.changeOuterArraySize(newType.getOuterArraySize()); -#endif // go to next member ++member; @@ -3476,7 +3476,7 @@ void TParseContext::redeclareBuiltinBlock(const TSourceLoc& loc, TTypeList& newT fixIoArraySize(loc, block->getWritableType()); // Save it in the AST for linker use. - trackLinkageDeferred(*block); + trackLinkage(*block); } void TParseContext::paramCheckFix(const TSourceLoc& loc, const TStorageQualifier& qualifier, TType& type) @@ -3958,9 +3958,7 @@ void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publi publicType.shaderQualifiers.layoutOverrideCoverage = true; return; } -#endif } -#ifdef NV_EXTENSIONS if (language == EShLangVertex || language == EShLangTessControl || language == EShLangTessEvaluation || @@ -3971,6 +3969,8 @@ void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publi return; } } +#else + } #endif error(loc, "unrecognized layout identifier, or qualifier requires assignment (e.g., binding = 4)", id.c_str(), ""); } @@ -4008,16 +4008,20 @@ void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publi // - uniform offsets // - atomic_uint offsets const char* feature = "offset"; - requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, feature); - const char* exts[2] = { E_GL_ARB_enhanced_layouts, E_GL_ARB_shader_atomic_counters }; - profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, 2, exts, feature); - profileRequires(loc, EEsProfile, 310, nullptr, feature); + if (spvVersion.spv == 0) { + requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, feature); + const char* exts[2] = { E_GL_ARB_enhanced_layouts, E_GL_ARB_shader_atomic_counters }; + profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, 2, exts, feature); + profileRequires(loc, EEsProfile, 310, nullptr, feature); + } publicType.qualifier.layoutOffset = value; return; } else if (id == "align") { const char* feature = "uniform buffer-member align"; - requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature); - profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature); + if (spvVersion.spv == 0) { + requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature); + profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature); + } // "The specified alignment must be a power of 2, or a compile-time error results." if (! IsPow2(value)) error(loc, "must be a power of 2", "align", ""); @@ -4495,8 +4499,11 @@ void TParseContext::layoutTypeCheck(const TSourceLoc& loc, const TType& type) } } } - } else if (type.isImage() && ! qualifier.writeonly) - error(loc, "image variables not declared 'writeonly' must have a format layout qualifier", "", ""); + } else if (type.isImage() && ! qualifier.writeonly) { + const char *explanation = "image variables declared 'writeonly' without a format layout qualifier"; + requireProfile(loc, ECoreProfile | ECompatibilityProfile, explanation); + profileRequires(loc, ECoreProfile | ECompatibilityProfile, 0, E_GL_EXT_shader_image_load_formatted, explanation); + } if (qualifier.layoutPushConstant && type.getBasicType() != EbtBlock) error(loc, "can only be used with a block", "push_constant", ""); @@ -5056,7 +5063,7 @@ TVariable* TParseContext::declareNonArray(const TSourceLoc& loc, TString& identi // add variable to symbol table if (symbolTable.insert(*variable)) { if (symbolTable.atGlobalLevel()) - trackLinkageDeferred(*variable); + trackLinkage(*variable); return variable; } @@ -5546,8 +5553,10 @@ void TParseContext::declareBlock(const TSourceLoc& loc, TTypeList& typeList, con if (memberType.isArray()) arrayUnsizedCheck(memberLoc, currentBlockQualifier, &memberType.getArraySizes(), false, member == typeList.size() - 1); if (memberQualifier.hasOffset()) { - requireProfile(memberLoc, ~EEsProfile, "offset on block member"); - profileRequires(memberLoc, ~EEsProfile, 440, E_GL_ARB_enhanced_layouts, "offset on block member"); + if (spvVersion.spv == 0) { + requireProfile(memberLoc, ~EEsProfile, "offset on block member"); + profileRequires(memberLoc, ~EEsProfile, 440, E_GL_ARB_enhanced_layouts, "offset on block member"); + } } if (memberType.containsOpaque()) @@ -5718,7 +5727,7 @@ void TParseContext::declareBlock(const TSourceLoc& loc, TTypeList& typeList, con fixIoArraySize(loc, variable.getWritableType()); // Save it in the AST for linker use. - trackLinkageDeferred(variable); + trackLinkage(variable); } // Do all block-declaration checking regarding the combination of in/out/uniform/buffer diff --git a/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.h b/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.h index 2e09a5ae9..ff4a3945b 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.h +++ b/src/libraries/glslang/glslang/MachineIndependent/ParseHelper.h @@ -79,9 +79,7 @@ public: symbolTable(symbolTable), parsingBuiltins(parsingBuiltins), scanContext(nullptr), ppContext(nullptr), globalUniformBlock(nullptr) - { - linkage = new TIntermAggregate; - } + { } virtual ~TParseContextBase() { } virtual void C_DECL error(const TSourceLoc&, const char* szReason, const char* szToken, @@ -141,7 +139,7 @@ public: // TODO: This could perhaps get its own object, but the current design doesn't work // yet when new uniform variables are declared between function definitions, so // this is pending getting a fully functional design. - virtual void growGlobalUniformBlock(TSourceLoc&, TType&, TString& memberName); + virtual void growGlobalUniformBlock(TSourceLoc&, TType&, TString& memberName, TTypeList* typeList = nullptr); virtual bool insertGlobalUniformBlock(); virtual bool lValueErrorCheck(const TSourceLoc&, const char* op, TIntermTyped*); @@ -183,13 +181,9 @@ protected: const char* szExtraInfoFormat, TPrefixType prefix, va_list args); virtual void trackLinkage(TSymbol& symbol); - virtual void trackLinkageDeferred(TSymbol& symbol); virtual void makeEditable(TSymbol*&); virtual TVariable* getEditableVariable(const char* name); virtual void finish(); - -private: - TIntermAggregate* linkage; }; // diff --git a/src/libraries/glslang/glslang/MachineIndependent/PoolAlloc.cpp b/src/libraries/glslang/glslang/MachineIndependent/PoolAlloc.cpp index 3502e9399..4007c3861 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/PoolAlloc.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/PoolAlloc.cpp @@ -105,8 +105,8 @@ void SetThreadPoolAllocator(TPoolAllocator& poolAllocator) TPoolAllocator::TPoolAllocator(int growthIncrement, int allocationAlignment) : pageSize(growthIncrement), alignment(allocationAlignment), - freeList(0), - inUseList(0), + freeList(nullptr), + inUseList(nullptr), numCalls(0) { // diff --git a/src/libraries/glslang/glslang/MachineIndependent/Scan.cpp b/src/libraries/glslang/glslang/MachineIndependent/Scan.cpp index 0e64364e2..c73f30d63 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/Scan.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/Scan.cpp @@ -1006,7 +1006,6 @@ int TScanContext::tokenizeIdentifier() case ISAMPLER1D: case ISAMPLER1DARRAY: - case SAMPLER1DARRAYSHADOW: case USAMPLER1D: case USAMPLER1DARRAY: afterType = true; @@ -1017,8 +1016,6 @@ int TScanContext::tokenizeIdentifier() case UVEC3: case UVEC4: case SAMPLERCUBESHADOW: - case SAMPLER2DARRAY: - case SAMPLER2DARRAYSHADOW: case ISAMPLER2D: case ISAMPLER3D: case ISAMPLERCUBE: @@ -1085,6 +1082,37 @@ int TScanContext::tokenizeIdentifier() reservedWord(); return keyword; + case SAMPLER1DARRAY: + case SAMPLER1DARRAYSHADOW: + afterType = true; + if (parseContext.symbolTable.atBuiltInLevel()) + return keyword; + else if (parseContext.profile == EEsProfile) { + if (parseContext.version >= 300) + reservedWord(); + else + return identifierOrType(); + } else if (parseContext.version < 130 && !parseContext.extensionTurnedOn(E_GL_EXT_texture_array)) { + if (parseContext.forwardCompatible) + parseContext.warn(loc, "using future keyword", tokenText, ""); + + return identifierOrType(); + } + return keyword; + + case SAMPLER2DARRAY: + case SAMPLER2DARRAYSHADOW: + afterType = true; + if (parseContext.symbolTable.atBuiltInLevel()) + return keyword; + else if (parseContext.version < 130 && ((parseContext.profile == EEsProfile && keyword != SAMPLER2DARRAY) || !parseContext.extensionTurnedOn(E_GL_EXT_texture_array))) { + if (parseContext.forwardCompatible) + parseContext.warn(loc, "using future keyword", tokenText, ""); + + return identifierOrType(); + } + return keyword; + case SAMPLER2DRECT: case SAMPLER2DRECTSHADOW: afterType = true; @@ -1098,15 +1126,6 @@ int TScanContext::tokenizeIdentifier() } return keyword; - case SAMPLER1DARRAY: - afterType = true; - if (parseContext.profile == EEsProfile && parseContext.version == 300) - reservedWord(); - else if ((parseContext.profile == EEsProfile && parseContext.version < 300) || - (parseContext.profile != EEsProfile && parseContext.version < 130)) - return identifierOrType(); - return keyword; - case SAMPLEREXTERNALOES: afterType = true; if (parseContext.symbolTable.atBuiltInLevel() || parseContext.extensionTurnedOn(E_GL_OES_EGL_image_external)) diff --git a/src/libraries/glslang/glslang/MachineIndependent/ShaderLang.cpp b/src/libraries/glslang/glslang/MachineIndependent/ShaderLang.cpp index dadac56a7..b0e55132a 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/ShaderLang.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/ShaderLang.cpp @@ -1632,6 +1632,7 @@ TProgram::TProgram() : pool(0), reflection(0), ioMapper(nullptr), linked(false) TProgram::~TProgram() { + delete ioMapper; delete infoSink; delete reflection; @@ -1707,6 +1708,15 @@ bool TProgram::linkStage(EShLanguage stage, EShMessages messages) intermediate[stage] = new TIntermediate(stage, firstIntermediate->getVersion(), firstIntermediate->getProfile()); + + + // The new TIntermediate must use the same origin as the original TIntermediates. + // Otherwise linking will fail due to different coordinate systems. + if (firstIntermediate->getOriginUpperLeft()) { + intermediate[stage]->setOriginUpperLeft(); + } + intermediate[stage]->setSpv(firstIntermediate->getSpv()); + newedIntermediate[stage] = true; } diff --git a/src/libraries/glslang/glslang/MachineIndependent/SymbolTable.h b/src/libraries/glslang/glslang/MachineIndependent/SymbolTable.h index 9028dd762..29ee40e15 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/SymbolTable.h +++ b/src/libraries/glslang/glslang/MachineIndependent/SymbolTable.h @@ -87,6 +87,12 @@ public: virtual const TString& getName() const { return *name; } virtual void changeName(const TString* newName) { name = newName; } + virtual void addPrefix(const char* prefix) + { + TString newName(prefix); + newName.append(*name); + changeName(NewPoolTString(newName.c_str())); + } virtual const TString& getMangledName() const { return getName(); } virtual TFunction* getAsFunction() { return 0; } virtual const TFunction* getAsFunction() const { return 0; } @@ -192,6 +198,7 @@ struct TParameter { TString *name; TType* type; TIntermTyped* defaultValue; + TBuiltInVariable declaredBuiltIn; void copyParam(const TParameter& param) { if (param.name) @@ -200,6 +207,7 @@ struct TParameter { name = 0; type = param.type->clone(); defaultValue = param.defaultValue; + declaredBuiltIn = param.declaredBuiltIn; } }; @@ -216,7 +224,11 @@ public: TSymbol(name), mangledName(*name + '('), op(tOp), - defined(false), prototyped(false), defaultParamCount(0) { returnType.shallowCopy(retType); } + defined(false), prototyped(false), defaultParamCount(0) + { + returnType.shallowCopy(retType); + declaredBuiltIn = retType.getQualifier().builtIn; + } virtual TFunction* clone() const; virtual ~TFunction(); @@ -226,15 +238,22 @@ public: virtual void addParameter(TParameter& p) { assert(writable); + p.declaredBuiltIn = p.type->getQualifier().builtIn; parameters.push_back(p); p.type->appendMangledName(mangledName); if (p.defaultValue != nullptr) defaultParamCount++; } + virtual void addPrefix(const char* prefix) override + { + TSymbol::addPrefix(prefix); + mangledName.insert(0, prefix); + } virtual const TString& getMangledName() const { return mangledName; } virtual const TType& getType() const { return returnType; } + virtual TBuiltInVariable getDeclaredBuiltInType() const { return declaredBuiltIn; } virtual TType& getWritableType() { return returnType; } virtual void relateToOperator(TOperator o) { assert(writable); op = o; } virtual TOperator getBuiltInOp() const { return op; } @@ -262,6 +281,8 @@ protected: typedef TVector TParamList; TParamList parameters; TType returnType; + TBuiltInVariable declaredBuiltIn; + TString mangledName; TOperator op; bool defined; diff --git a/src/libraries/glslang/glslang/MachineIndependent/Versions.cpp b/src/libraries/glslang/glslang/MachineIndependent/Versions.cpp index 9025e7612..339b0baa6 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/Versions.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/Versions.cpp @@ -156,6 +156,7 @@ void TParseVersions::initializeExtensionBehavior() extensionBehavior[E_GL_OES_EGL_image_external] = EBhDisable; extensionBehavior[E_GL_EXT_shader_texture_lod] = EBhDisable; + extensionBehavior[E_GL_EXT_texture_array] = EBhDisable; extensionBehavior[E_GL_ARB_texture_rectangle] = EBhDisable; extensionBehavior[E_GL_3DL_array_objects] = EBhDisable; extensionBehavior[E_GL_ARB_shading_language_420pack] = EBhDisable; @@ -182,6 +183,7 @@ void TParseVersions::initializeExtensionBehavior() // extensionBehavior[E_GL_ARB_cull_distance] = EBhDisable; // present for 4.5, but need extension control over block members extensionBehavior[E_GL_EXT_shader_non_constant_global_initializers] = EBhDisable; + extensionBehavior[E_GL_EXT_shader_image_load_formatted] = EBhDisable; // #line and #include extensionBehavior[E_GL_GOOGLE_cpp_style_line_directive] = EBhDisable; @@ -201,6 +203,7 @@ void TParseVersions::initializeExtensionBehavior() extensionBehavior[E_GL_ARB_shader_viewport_layer_array] = EBhDisable; extensionBehavior[E_GL_NV_viewport_array2] = EBhDisable; extensionBehavior[E_GL_NV_stereo_view_rendering] = EBhDisable; + extensionBehavior[E_GL_NVX_multiview_per_view_attributes] = EBhDisable; #endif // AEP @@ -240,6 +243,7 @@ void TParseVersions::getPreamble(std::string& preamble) preamble = "#define GL_ES 1\n" "#define GL_FRAGMENT_PRECISION_HIGH 1\n" + "#define GL_EXT_texture_array 1\n" "#define GL_OES_texture_3D 1\n" "#define GL_OES_standard_derivatives 1\n" "#define GL_EXT_frag_depth 1\n" @@ -278,6 +282,7 @@ void TParseVersions::getPreamble(std::string& preamble) } else { preamble = "#define GL_FRAGMENT_PRECISION_HIGH 1\n" + "#define GL_EXT_texture_array 1\n" "#define GL_ARB_texture_rectangle 1\n" "#define GL_ARB_shading_language_420pack 1\n" "#define GL_ARB_texture_gather 1\n" @@ -302,6 +307,7 @@ void TParseVersions::getPreamble(std::string& preamble) "#define GL_ARB_sparse_texture_clamp 1\n" // "#define GL_ARB_cull_distance 1\n" // present for 4.5, but need extension control over block members "#define GL_EXT_shader_non_constant_global_initializers 1\n" + "#define GL_EXT_shader_image_load_formatted 1\n" #ifdef AMD_EXTENSIONS "#define GL_AMD_shader_ballot 1\n" diff --git a/src/libraries/glslang/glslang/MachineIndependent/Versions.h b/src/libraries/glslang/glslang/MachineIndependent/Versions.h index cf8f5e23d..0498deffa 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/Versions.h +++ b/src/libraries/glslang/glslang/MachineIndependent/Versions.h @@ -104,6 +104,7 @@ const char* const E_GL_EXT_frag_depth = "GL_EXT_frag_depth"; const char* const E_GL_OES_EGL_image_external = "GL_OES_EGL_image_external"; const char* const E_GL_EXT_shader_texture_lod = "GL_EXT_shader_texture_lod"; +const char* const E_GL_EXT_texture_array = "GL_EXT_texture_array"; const char* const E_GL_ARB_texture_rectangle = "GL_ARB_texture_rectangle"; const char* const E_GL_3DL_array_objects = "GL_3DL_array_objects"; const char* const E_GL_ARB_shading_language_420pack = "GL_ARB_shading_language_420pack"; @@ -130,6 +131,7 @@ const char* const E_GL_ARB_sparse_texture_clamp = "GL_ARB_sparse_texture // const char* const E_GL_ARB_cull_distance = "GL_ARB_cull_distance"; // present for 4.5, but need extension control over block members const char* const E_GL_EXT_shader_non_constant_global_initializers = "GL_EXT_shader_non_constant_global_initializers"; +const char* const E_GL_EXT_shader_image_load_formatted = "GL_EXT_shader_image_load_formatted"; // #line and #include const char* const E_GL_GOOGLE_cpp_style_line_directive = "GL_GOOGLE_cpp_style_line_directive"; @@ -149,6 +151,7 @@ const char* const E_SPV_NV_geometry_shader_passthrough = "GL_NV_geometr const char* const E_GL_ARB_shader_viewport_layer_array = "GL_ARB_shader_viewport_layer_array"; const char* const E_GL_NV_viewport_array2 = "GL_NV_viewport_array2"; const char* const E_GL_NV_stereo_view_rendering = "GL_NV_stereo_view_rendering"; +const char* const E_GL_NVX_multiview_per_view_attributes = "GL_NVX_multiview_per_view_attributes"; // Arrays of extensions for the above viewportEXTs duplications diff --git a/src/libraries/glslang/glslang/MachineIndependent/iomapper.cpp b/src/libraries/glslang/glslang/MachineIndependent/iomapper.cpp index a2b353183..04868f16a 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/iomapper.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/iomapper.cpp @@ -209,6 +209,9 @@ struct TResolverAdaptor TIoMapResolver& resolver; TInfoSink& infoSink; bool& error; + +private: + TResolverAdaptor& operator=(TResolverAdaptor&); }; /* diff --git a/src/libraries/glslang/glslang/MachineIndependent/localintermediate.h b/src/libraries/glslang/glslang/MachineIndependent/localintermediate.h index ec58d50d4..14193f56a 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/localintermediate.h +++ b/src/libraries/glslang/glslang/MachineIndependent/localintermediate.h @@ -252,7 +252,7 @@ public: TIntermAggregate* makeAggregate(const TSourceLoc&); TIntermTyped* setAggregateOperator(TIntermNode*, TOperator, const TType& type, TSourceLoc); bool areAllChildConst(TIntermAggregate* aggrNode); - TIntermNode* addSelection(TIntermTyped* cond, TIntermNodePair code, const TSourceLoc&); + TIntermTyped* addSelection(TIntermTyped* cond, TIntermNodePair code, const TSourceLoc&); TIntermTyped* addSelection(TIntermTyped* cond, TIntermTyped* trueBlock, TIntermTyped* falseBlock, const TSourceLoc&); TIntermTyped* addComma(TIntermTyped* left, TIntermTyped* right, const TSourceLoc&); TIntermTyped* addMethod(TIntermTyped*, const TType&, const TString*, const TSourceLoc&); @@ -263,6 +263,7 @@ public: TIntermConstantUnion* addConstantUnion(unsigned long long, const TSourceLoc&, bool literal = false) const; TIntermConstantUnion* addConstantUnion(bool, const TSourceLoc&, bool literal = false) const; TIntermConstantUnion* addConstantUnion(double, TBasicType, const TSourceLoc&, bool literal = false) const; + TIntermConstantUnion* addConstantUnion(const TString*, const TSourceLoc&, bool literal = false) const; TIntermTyped* promoteConstantUnion(TBasicType, TIntermConstantUnion*) const; bool parseConstTree(TIntermNode*, TConstUnionArray, TOperator, const TType&, bool singleConstantParam = false); TIntermLoop* addLoop(TIntermNode*, TIntermTyped*, TIntermTyped*, bool testFirst, const TSourceLoc&); @@ -440,6 +441,7 @@ protected: bool promoteAggregate(TIntermAggregate&); void pushSelector(TIntermSequence&, const TVectorSelector&, const TSourceLoc&); void pushSelector(TIntermSequence&, const TMatrixSelector&, const TSourceLoc&); + bool specConstantPropagates(const TIntermTyped&, const TIntermTyped&); const EShLanguage language; // stage, known at construction time EShSource source; // source language, known a bit later diff --git a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp index abce3b564..ef8e83e17 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp @@ -148,10 +148,10 @@ int TPpContext::CPPdefine(TPpToken* ppToken) // record the definition of the macro TSourceLoc defineLoc = ppToken->loc; // because ppToken is going to go to the next line before we report errors while (token != '\n' && token != EndOfInput) { - RecordToken(mac.body, token, ppToken); + mac.body.putToken(token, ppToken); token = scanToken(ppToken); if (token != '\n' && ppToken->space) - RecordToken(mac.body, ' ', ppToken); + mac.body.putToken(' ', ppToken); } // check for duplicate definition @@ -166,15 +166,15 @@ int TPpContext::CPPdefine(TPpToken* ppToken) else { if (existing->args != mac.args) parseContext.ppError(defineLoc, "Macro redefined; different argument names:", "#define", atomStrings.getString(defAtom)); - RewindTokenStream(existing->body); - RewindTokenStream(mac.body); + existing->body.reset(); + mac.body.reset(); int newToken; do { int oldToken; TPpToken oldPpToken; TPpToken newPpToken; - oldToken = ReadToken(existing->body, &oldPpToken); - newToken = ReadToken(mac.body, &newPpToken); + oldToken = existing->body.getToken(parseContext, &oldPpToken); + newToken = mac.body.getToken(parseContext, &newPpToken); if (oldToken != newToken || oldPpToken != newPpToken) { parseContext.ppError(defineLoc, "Macro redefined; different substitutions:", "#define", atomStrings.getString(defAtom)); break; @@ -979,28 +979,16 @@ int TPpContext::scanHeaderName(TPpToken* ppToken, char delimit) // Returns nullptr if no expanded argument is created. TPpContext::TokenStream* TPpContext::PrescanMacroArg(TokenStream& arg, TPpToken* ppToken, bool newLineOkay) { - // pre-check, to see if anything in the argument needs to be expanded, - // to see if we can kick out early - int token; - RewindTokenStream(arg); - do { - token = ReadToken(arg, ppToken); - if (token == PpAtomIdentifier && lookupMacroDef(atomStrings.getAtom(ppToken->name)) != nullptr) - break; - } while (token != EndOfInput); - - // if nothing needs to be expanded, kick out early - if (token == EndOfInput) - return nullptr; - // expand the argument TokenStream* expandedArg = new TokenStream; pushInput(new tMarkerInput(this)); pushTokenStreamInput(arg); + int token; while ((token = scanToken(ppToken)) != tMarkerInput::marker && token != EndOfInput) { + token = tokenPaste(token, *ppToken); if (token == PpAtomIdentifier && MacroExpand(ppToken, false, newLineOkay) != 0) continue; - RecordToken(*expandedArg, token, ppToken); + expandedArg->putToken(token, ppToken); } if (token == EndOfInput) { @@ -1023,7 +1011,7 @@ int TPpContext::tMacroInput::scan(TPpToken* ppToken) { int token; do { - token = pp->ReadToken(mac->body, ppToken); + token = mac->body.getToken(pp->parseContext, ppToken); } while (token == ' '); // handle white space in macro // Hash operators basically turn off a round of macro substitution @@ -1054,7 +1042,7 @@ int TPpContext::tMacroInput::scan(TPpToken* ppToken) } // see if are preceding a ## - if (peekMacPasting()) { + if (mac->body.peekUntokenizedPasting()) { prepaste = true; pasting = true; } @@ -1081,31 +1069,6 @@ int TPpContext::tMacroInput::scan(TPpToken* ppToken) return token; } -// See if the next non-white-space token in the macro is ## -bool TPpContext::tMacroInput::peekMacPasting() -{ - // don't return early, have to restore this - size_t savePos = mac->body.current; - - // skip white-space - int ltoken; - do { - ltoken = pp->lReadByte(mac->body); - } while (ltoken == ' '); - - // check for ## - bool pasting = false; - if (ltoken == '#') { - ltoken = pp->lReadByte(mac->body); - if (ltoken == '#') - pasting = true; - } - - mac->body.current = savePos; - - return pasting; -} - // return a textual zero, for scanning a macro that was never defined int TPpContext::tZeroInput::scan(TPpToken* ppToken) { @@ -1230,7 +1193,7 @@ int TPpContext::MacroExpand(TPpToken* ppToken, bool expandUndef, bool newLineOka depth++; if (token == ')') depth--; - RecordToken(*in->args[arg], token, ppToken); + in->args[arg]->putToken(token, ppToken); tokenRecorded = true; } if (token == ')') { @@ -1263,14 +1226,14 @@ int TPpContext::MacroExpand(TPpToken* ppToken, bool expandUndef, bool newLineOka } // We need both expanded and non-expanded forms of the argument, for whether or - // not token pasting is in play. + // not token pasting will be applied later when the argument is consumed next to ##. for (size_t i = 0; i < in->mac->args.size(); i++) in->expandedArgs[i] = PrescanMacroArg(*in->args[i], ppToken, newLineOkay); } pushInput(in); macro->busy = 1; - RewindTokenStream(macro->body); + macro->body.reset(); return 1; } diff --git a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpContext.h b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpContext.h index 15e54b79f..f7b352200 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpContext.h +++ b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpContext.h @@ -220,8 +220,26 @@ public: inputStack.pop_back(); } - struct TokenStream { + // + // From PpTokens.cpp + // + + class TokenStream { + public: TokenStream() : current(0) { } + + void putToken(int token, TPpToken* ppToken); + int getToken(TParseContextBase&, TPpToken*); + bool atEnd() { return current >= data.size(); } + bool peekTokenizedPasting(bool lastTokenPastes); + bool peekUntokenizedPasting(); + void reset() { current = 0; } + + protected: + void putSubtoken(int); + int getSubtoken(); + void ungetSubtoken(); + TVector data; size_t current; }; @@ -306,14 +324,13 @@ protected: virtual int getch() override { assert(0); return EndOfInput; } virtual void ungetch() override { assert(0); } bool peekPasting() override { return prepaste; } - bool endOfReplacementList() override { return mac->body.current >= mac->body.data.size(); } + bool endOfReplacementList() override { return mac->body.atEnd(); } MacroSymbol *mac; TVector args; TVector expandedArgs; protected: - bool peekMacPasting(); bool prepaste; // true if we are just before ## bool postpaste; // true if we are right after ## }; @@ -375,22 +392,16 @@ protected: // // From PpTokens.cpp // - void lAddByte(TokenStream&, unsigned char fVal); - int lReadByte(TokenStream&); - void lUnreadByte(TokenStream&); - void RecordToken(TokenStream&, int token, TPpToken* ppToken); - void RewindTokenStream(TokenStream&); - int ReadToken(TokenStream&, TPpToken*); void pushTokenStreamInput(TokenStream&, bool pasting = false); void UngetToken(int token, TPpToken*); class tTokenInput : public tInput { public: tTokenInput(TPpContext* pp, TokenStream* t, bool prepasting) : tInput(pp), tokens(t), lastTokenPastes(prepasting) { } - virtual int scan(TPpToken *) override; + virtual int scan(TPpToken *ppToken) override { return tokens->getToken(pp->parseContext, ppToken); } virtual int getch() override { assert(0); return EndOfInput; } virtual void ungetch() override { assert(0); } - virtual bool peekPasting() override; + virtual bool peekPasting() override { return tokens->peekTokenizedPasting(lastTokenPastes); } protected: TokenStream* tokens; bool lastTokenPastes; // true if the last token in the input is to be pasted, rather than consumed as a token diff --git a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp index 83e6c89cc..a30442400 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp @@ -232,6 +232,8 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken) switch (ch) { default: // Single character token, including EndOfInput, '#' and '\' (escaped newlines are handled at a lower level, so this is just a '\' token) + if (ch > PpAtomMaxSingle) + ch = PpAtomBadToken; return ch; case 'A': case 'B': case 'C': case 'D': case 'E': diff --git a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp index f0441b7f8..32d2b0b74 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp +++ b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp @@ -95,48 +95,45 @@ NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace glslang { -void TPpContext::lAddByte(TokenStream& fTok, unsigned char fVal) +// push onto back of stream +void TPpContext::TokenStream::putSubtoken(int subtoken) { - fTok.data.push_back(fVal); + assert((subtoken & ~0xff) == 0); + data.push_back(static_cast(subtoken)); } -/* -* Get the next byte from a stream. -*/ -int TPpContext::lReadByte(TokenStream& pTok) +// get the next token in stream +int TPpContext::TokenStream::getSubtoken() { - if (pTok.current < pTok.data.size()) - return pTok.data[pTok.current++]; + if (current < data.size()) + return data[current++]; else return EndOfInput; } -void TPpContext::lUnreadByte(TokenStream& pTok) +// back up one position in the stream +void TPpContext::TokenStream::ungetSubtoken() { - if (pTok.current > 0) - --pTok.current; + if (current > 0) + --current; } -/* -* Add a token to the end of a list for later playback. -*/ -void TPpContext::RecordToken(TokenStream& pTok, int token, TPpToken* ppToken) +// Add a complete token (including backing string) to the end of a list +// for later playback. +void TPpContext::TokenStream::putToken(int token, TPpToken* ppToken) { const char* s; char* str = NULL; - if (token > PpAtomMaxSingle) - lAddByte(pTok, (unsigned char)((token & 0x7f) + 0x80)); - else - lAddByte(pTok, (unsigned char)(token & 0x7f)); + putSubtoken(token); switch (token) { case PpAtomIdentifier: case PpAtomConstString: s = ppToken->name; while (*s) - lAddByte(pTok, (unsigned char) *s++); - lAddByte(pTok, 0); + putSubtoken(*s++); + putSubtoken(0); break; case PpAtomConstInt: case PpAtomConstUint: @@ -149,46 +146,35 @@ void TPpContext::RecordToken(TokenStream& pTok, int token, TPpToken* ppToken) #endif str = ppToken->name; while (*str) { - lAddByte(pTok, (unsigned char) *str); + putSubtoken(*str); str++; } - lAddByte(pTok, 0); + putSubtoken(0); break; default: break; } } -/* -* Reset a token stream in preparation for reading. -*/ -void TPpContext::RewindTokenStream(TokenStream& pTok) +// Read the next token from a token stream. +// (Not the source stream, but a stream used to hold a tokenized macro). +int TPpContext::TokenStream::getToken(TParseContextBase& parseContext, TPpToken *ppToken) { - pTok.current = 0; -} - -/* -* Read the next token from a token stream (not the source stream, but stream used to hold a tokenized macro). -*/ -int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken) -{ - int ltoken, len; + int len; int ch; - ltoken = lReadByte(pTok); + int subtoken = getSubtoken(); ppToken->loc = parseContext.getCurrentLoc(); - if (ltoken > 127) - ltoken += 128; - switch (ltoken) { + switch (subtoken) { case '#': // Check for ##, unless the current # is the last character - if (pTok.current < pTok.data.size()) { - if (lReadByte(pTok) == '#') { + if (current < data.size()) { + if (getSubtoken() == '#') { parseContext.requireProfile(ppToken->loc, ~EEsProfile, "token pasting (##)"); parseContext.profileRequires(ppToken->loc, ~EEsProfile, 130, 0, "token pasting (##)"); - ltoken = PpAtomPaste; + subtoken = PpAtomPaste; } else - lUnreadByte(pTok); + ungetSubtoken(); } break; case PpAtomConstString: @@ -203,12 +189,12 @@ int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken) case PpAtomConstInt64: case PpAtomConstUint64: len = 0; - ch = lReadByte(pTok); + ch = getSubtoken(); while (ch != 0 && ch != EndOfInput) { if (len < MaxTokenLength) { ppToken->name[len] = (char)ch; len++; - ch = lReadByte(pTok); + ch = getSubtoken(); } else { parseContext.error(ppToken->loc, "token too long", "", ""); break; @@ -216,7 +202,7 @@ int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken) } ppToken->name[len] = 0; - switch (ltoken) { + switch (subtoken) { case PpAtomIdentifier: break; case PpAtomConstString: @@ -267,43 +253,80 @@ int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken) } } - return ltoken; + return subtoken; } -int TPpContext::tTokenInput::scan(TPpToken* ppToken) +// We are pasting if +// 1. we are preceding a pasting operator within this stream +// or +// 2. the entire macro is preceding a pasting operator (lastTokenPastes) +// and we are also on the last token +bool TPpContext::TokenStream::peekTokenizedPasting(bool lastTokenPastes) { - return pp->ReadToken(*tokens, ppToken); -} + // 1. preceding ##? + + size_t savePos = current; + int subtoken; + // skip white space + do { + subtoken = getSubtoken(); + } while (subtoken == ' '); + current = savePos; + if (subtoken == PpAtomPaste) + return true; + + // 2. last token and we've been told after this there will be a ## -// We are pasting if the entire macro is preceding a pasting operator -// (lastTokenPastes) and we are also on the last token. -bool TPpContext::tTokenInput::peekPasting() -{ if (! lastTokenPastes) return false; - // Getting here means the last token will be pasted. + // Getting here means the last token will be pasted, after this // Are we at the last non-whitespace token? - size_t savePos = tokens->current; + savePos = current; bool moreTokens = false; do { - int byte = pp->lReadByte(*tokens); - if (byte == EndOfInput) + subtoken = getSubtoken(); + if (subtoken == EndOfInput) break; - if (byte != ' ') { + if (subtoken != ' ') { moreTokens = true; break; } } while (true); - tokens->current = savePos; + current = savePos; return !moreTokens; } +// See if the next non-white-space tokens are two consecutive # +bool TPpContext::TokenStream::peekUntokenizedPasting() +{ + // don't return early, have to restore this + size_t savePos = current; + + // skip white-space + int subtoken; + do { + subtoken = getSubtoken(); + } while (subtoken == ' '); + + // check for ## + bool pasting = false; + if (subtoken == '#') { + subtoken = getSubtoken(); + if (subtoken == '#') + pasting = true; + } + + current = savePos; + + return pasting; +} + void TPpContext::pushTokenStreamInput(TokenStream& ts, bool prepasting) { pushInput(new tTokenInput(this, &ts, prepasting)); - RewindTokenStream(ts); + ts.reset(); } int TPpContext::tUngotTokenInput::scan(TPpToken* ppToken) diff --git a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.h b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.h index 3b1367f3b..9695c2fcc 100755 --- a/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.h +++ b/src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.h @@ -82,7 +82,11 @@ namespace glslang { // Multi-character tokens enum EFixedAtoms { - PpAtomMaxSingle = 256, // single character tokens get their own char value as their token, skip them + // single character tokens get their own char value as their token; start here for multi-character tokens + PpAtomMaxSingle = 127, + + // replace bad character tokens with this, to avoid accidental aliasing with the below + PpAtomBadToken, // Operators diff --git a/src/libraries/glslang/glslang/Public/ShaderLang.h b/src/libraries/glslang/glslang/Public/ShaderLang.h index 6c3fb11cb..d2f4e1b5c 100755 --- a/src/libraries/glslang/glslang/Public/ShaderLang.h +++ b/src/libraries/glslang/glslang/Public/ShaderLang.h @@ -540,6 +540,7 @@ protected: bool linked; private: + TProgram(TProgram&); TProgram& operator=(TProgram&); }; diff --git a/src/modules/graphics/Canvas.cpp b/src/modules/graphics/Canvas.cpp index a42f4e951..09f5d0f41 100644 --- a/src/modules/graphics/Canvas.cpp +++ b/src/modules/graphics/Canvas.cpp @@ -29,7 +29,8 @@ namespace graphics love::Type Canvas::type("Canvas", &Texture::type); int Canvas::canvasCount = 0; -Canvas::Canvas() +Canvas::Canvas(TextureType textype) + : Texture(textype) { canvasCount++; } diff --git a/src/modules/graphics/Canvas.h b/src/modules/graphics/Canvas.h index 53b16d853..377d2651c 100644 --- a/src/modules/graphics/Canvas.h +++ b/src/modules/graphics/Canvas.h @@ -39,15 +39,19 @@ public: struct Settings { + int width = 1; + int height = 1; + int layers = 1; // depth for 3D textures PixelFormat format = PIXELFORMAT_NORMAL; + TextureType type = TEXTURE_2D; float pixeldensity = 1.0f; int msaa = 0; }; - Canvas(); + Canvas(TextureType textype); virtual ~Canvas(); - virtual love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h) = 0; + virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) = 0; virtual int getMSAA() const = 0; virtual int getRequestedMSAA() const = 0; diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index 41d51a495..d1537fba1 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -81,6 +81,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &f) if (!r->hasGlyph(9)) // No tab character in the Rasterizer. useSpacesAsTab = true; + loadVolatile(); ++fontCount; } @@ -113,6 +114,72 @@ Font::TextureSize Font::getNextTextureSize() const return size; } +bool Font::loadVolatile() +{ + textureCacheID++; + createTexture(); + return true; +} + +void Font::createTexture() +{ + auto gfx = Module::getInstance(Module::M_GRAPHICS); + gfx->flushStreamDraws(); + + Image *image = nullptr; + TextureSize size = {textureWidth, textureHeight}; + TextureSize nextsize = getNextTextureSize(); + bool recreatetexture = false; + + // If we have an existing texture already, we'll try replacing it with a + // larger-sized one rather than creating a second one. Having a single + // texture reduces texture switches and draw calls when rendering. + if ((nextsize.width > size.width || nextsize.height > size.height) && !images.empty()) + { + recreatetexture = true; + size = nextsize; + images.pop_back(); + } + + Image::Settings settings; + image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings); + image->setFilter(filter); + + // Initialize the texture with transparent black. + size_t bpp = getPixelFormatSize(pixelFormat); + std::vector emptydata(size.width * size.height * bpp, 0); + + Rect rect = {0, 0, size.width, size.height}; + image->replacePixels(emptydata.data(), emptydata.size(), rect, 0, 0, false); + + images.emplace_back(image, Acquire::NORETAIN); + + textureWidth = size.width; + textureHeight = size.height; + + rowHeight = textureX = textureY = TEXTURE_PADDING; + + // Re-add the old glyphs if we re-created the existing texture object. + if (recreatetexture) + { + textureCacheID++; + + std::vector glyphstoadd; + + for (const auto &glyphpair : glyphs) + glyphstoadd.push_back(glyphpair.first); + + glyphs.clear(); + + for (uint32 g : glyphstoadd) + addGlyph(g); + } +} + +void Font::unloadVolatile() +{ +} + love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph) { // Use spaces for the tab 'glyph'. @@ -178,7 +245,11 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) // Don't waste space for empty glyphs. if (w > 0 && h > 0) { - uploadGlyphToTexture(gd, g); + Image *image = images.back(); + g.texture = image; + + Rect rect = {textureX, textureY, gd->getWidth(), gd->getHeight()}; + image->replacePixels(gd->getData(), gd->getSize(), rect, 0, 0, false); double tX = (double) textureX, tY = (double) textureY; double tWidth = (double) textureWidth, tHeight = (double) textureHeight; @@ -535,7 +606,7 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vectorrequestStreamDraw(req); GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0]; @@ -809,6 +880,14 @@ float Font::getLineHeight() const return lineHeight; } +void Font::setFilter(const Texture::Filter &f) +{ + for (const auto &image : images) + image->setFilter(f); + + filter = f; +} + const Texture::Filter &Font::getFilter() const { return filter; diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index 75ca5d85a..af19f74c4 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -33,8 +33,9 @@ #include "common/Vector.h" #include "font/Rasterizer.h" -#include "Texture.h" +#include "Image.h" #include "vertex.h" +#include "Volatile.h" namespace love { @@ -43,7 +44,7 @@ namespace graphics class Graphics; -class Font : public Object +class Font : public Object, public Volatile { public: @@ -88,7 +89,7 @@ public: // Used to determine when to change textures in the generated vertex array. struct DrawCommand { - ptrdiff_t texture; + Texture *texture; int startvertex; int vertexcount; }; @@ -155,7 +156,7 @@ public: **/ float getLineHeight() const; - virtual void setFilter(const Texture::Filter &f) = 0; + void setFilter(const Texture::Filter &f); const Texture::Filter &getFilter() const; // Extra font metrics @@ -172,16 +173,20 @@ public: uint32 getTextureCacheID() const; + // Implements Volatile. + bool loadVolatile() override; + void unloadVolatile() override; + static bool getConstant(const char *in, AlignMode &out); static bool getConstant(AlignMode in, const char *&out); static int fontCount; -protected: +private: struct Glyph { - ptrdiff_t texture; + Texture *texture; int spacing; GlyphVertex vertices[4]; }; @@ -192,8 +197,7 @@ protected: int height; }; - virtual void createTexture() = 0; - virtual void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) = 0; + void createTexture(); TextureSize getNextTextureSize() const; love::font::GlyphData *getRasterizerGlyphData(uint32 glyph); @@ -210,6 +214,8 @@ protected: int textureWidth; int textureHeight; + std::vector> images; + // maps glyphs to glyph texture information std::unordered_map glyphs; @@ -226,15 +232,15 @@ protected: int rowHeight; bool useSpacesAsTab; - + // ID which is incremented when the texture cache is invalidated. uint32 textureCacheID; - + static const int TEXTURE_PADDING = 1; - + // This will be used if the Rasterizer doesn't have a tab character itself. static const int SPACES_PER_TAB = 4; - + static StringMap::Entry alignModeEntries[]; static StringMap alignModes; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index a076ec7d7..d1e4c172f 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -25,6 +25,8 @@ #include "Polyline.h" #include "font/Font.h" #include "window/Window.h" +#include "Font.h" +#include "Video.h" // C++ #include @@ -152,6 +154,16 @@ Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh) return new Quad(v, sw, sh); } +Font *Graphics::newFont(love::font::Rasterizer *data, const Texture::Filter &filter) +{ + return new Font(data, filter); +} + +Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity) +{ + return new Video(this, stream, pixeldensity); +} + bool Graphics::validateShader(bool gles, const Shader::ShaderSource &source, std::string &err) { return Shader::validate(this, gles, source, true, err); @@ -179,9 +191,9 @@ int Graphics::getPixelHeight() const double Graphics::getCurrentPixelDensity() const { - if (states.back().canvases.size() > 0) + if (states.back().renderTargets.size() > 0) { - love::graphics::Canvas *c = states.back().canvases[0]; + love::graphics::Canvas *c = states.back().renderTargets[0].canvas; return (double) c->getPixelHeight() / (double) c->getHeight(); } @@ -240,7 +252,7 @@ void Graphics::restoreState(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - setCanvas(s.canvases); + setCanvas(s.renderTargets); setColorMask(s.colorMask); setWireframe(s.wireframe); @@ -283,12 +295,14 @@ void Graphics::restoreStateChecked(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - bool canvaseschanged = s.canvases.size() != cur.canvases.size(); + bool canvaseschanged = s.renderTargets.size() != cur.renderTargets.size(); if (!canvaseschanged) { - for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++) + for (size_t i = 0; i < s.renderTargets.size() && i < cur.renderTargets.size(); i++) { - if (s.canvases[i].get() != cur.canvases[i].get()) + const auto &rt1 = s.renderTargets[i]; + const auto &rt2 = cur.renderTargets[i]; + if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice) { canvaseschanged = true; break; @@ -297,7 +311,7 @@ void Graphics::restoreStateChecked(const DisplayState &s) } if (canvaseschanged) - setCanvas(s.canvases); + setCanvas(s.renderTargets); if (s.colorMask != cur.colorMask) setColorMask(s.colorMask); @@ -386,47 +400,47 @@ love::graphics::Shader *Graphics::getShader() const return states.back().shader.get(); } -void Graphics::setCanvas(Canvas *canvas) +void Graphics::setCanvas(RenderTarget rt) { - if (canvas == nullptr) + if (rt.canvas == nullptr) return setCanvas(); - std::vector canvases = {canvas}; - setCanvas(canvases); + std::vector rts = {rt}; + setCanvas(rts); } -void Graphics::setCanvas(const std::vector> &canvases) +void Graphics::setCanvas(const std::vector &rts) { - std::vector canvaslist; - canvaslist.reserve(canvases.size()); + std::vector canvaslist; + canvaslist.reserve(rts.size()); - for (const StrongRef &c : canvases) - canvaslist.push_back(c.get()); + for (const auto &rt : rts) + canvaslist.emplace_back(rt.canvas.get(), rt.slice); return setCanvas(canvaslist); } -std::vector Graphics::getCanvas() const +std::vector Graphics::getCanvas() const { - std::vector canvases; - canvases.reserve(states.back().canvases.size()); + std::vector rts; + rts.reserve(states.back().renderTargets.size()); - for (const StrongRef &c : states.back().canvases) - canvases.push_back(c.get()); + for (const auto &rt : states.back().renderTargets) + rts.emplace_back(rt.canvas.get(), rt.slice); - return canvases; + return rts; } bool Graphics::isCanvasActive() const { - return !states.back().canvases.empty(); + return !states.back().renderTargets.empty(); } bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const { - for (const auto &c : states.back().canvases) + for (const auto &rt : states.back().renderTargets) { - if (c.get() == canvas) + if (rt.canvas.get() == canvas) return true; } @@ -563,7 +577,7 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest & if (req.primitiveMode != state.primitiveMode || req.formats[0] != state.formats[0] || req.formats[1] != state.formats[1] || ((req.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0)) - || req.texture != state.texture || req.textureHandle != state.textureHandle) + || req.texture != state.texture) { shouldflush = true; } @@ -622,7 +636,6 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest & state.formats[0] = req.formats[0]; state.formats[1] = req.formats[1]; state.texture = req.texture; - state.textureHandle = req.textureHandle; } if (shouldresize) @@ -1358,6 +1371,8 @@ StringMap::Entry Graphics::featur { "lighten", FEATURE_LIGHTEN }, { "fullnpot", FEATURE_FULL_NPOT }, { "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP }, + { "arraytexture", FEATURE_ARRAY_TEXTURE }, + { "volumetexture", FEATURE_VOLUME_TEXTURE }, { "glsl3", FEATURE_GLSL3 }, { "instancing", FEATURE_INSTANCING }, }; @@ -1366,11 +1381,14 @@ StringMap Graphics::features(Grap StringMap::Entry Graphics::systemLimitEntries[] = { - { "pointsize", LIMIT_POINT_SIZE }, - { "texturesize", LIMIT_TEXTURE_SIZE }, - { "multicanvas", LIMIT_MULTI_CANVAS }, - { "canvasmsaa", LIMIT_CANVAS_MSAA }, - { "anisotropy", LIMIT_ANISOTROPY }, + { "pointsize", LIMIT_POINT_SIZE }, + { "texturesize", LIMIT_TEXTURE_SIZE }, + { "texturelayers", LIMIT_TEXTURE_LAYERS }, + { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE }, + { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE }, + { "multicanvas", LIMIT_MULTI_CANVAS }, + { "canvasmsaa", LIMIT_CANVAS_MSAA }, + { "anisotropy", LIMIT_ANISOTROPY }, }; StringMap Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries)); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 516fb54b3..509a62736 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -177,6 +177,8 @@ public: FEATURE_LIGHTEN, FEATURE_FULL_NPOT, FEATURE_PIXEL_SHADER_HIGHP, + FEATURE_ARRAY_TEXTURE, + FEATURE_VOLUME_TEXTURE, FEATURE_GLSL3, FEATURE_INSTANCING, FEATURE_MAX_ENUM @@ -193,6 +195,9 @@ public: { LIMIT_POINT_SIZE, LIMIT_TEXTURE_SIZE, + LIMIT_VOLUME_TEXTURE_SIZE, + LIMIT_CUBE_TEXTURE_SIZE, + LIMIT_TEXTURE_LAYERS, LIMIT_MULTI_CANVAS, LIMIT_CANVAS_MSAA, LIMIT_ANISOTROPY, @@ -262,10 +267,6 @@ public: int vertexCount = 0; Texture *texture = nullptr; - // FIXME: This is only needed for fonts. We should just change fonts to - // use love.graphics Images instead of raw OpenGL textures. - ptrdiff_t textureHandle = 0; - StreamDrawRequest() { // VS2013 can't initialize arrays in the above manner... @@ -312,29 +313,48 @@ public: Reference *ref; }; + struct RenderTarget + { + Canvas *canvas = nullptr; + int slice = 0; + + RenderTarget(Canvas *canvas, int slice = 0) + : canvas(canvas) + , slice(slice) + {} + }; + + struct RenderTargetStrongRef + { + StrongRef canvas; + int slice = 0; + + RenderTargetStrongRef(Canvas *canvas, int slice = 0) + : canvas(canvas) + , slice(slice) + {} + }; + Graphics(); virtual ~Graphics(); // Implements Module. virtual ModuleType getModuleType() const { return M_GRAPHICS; } - virtual Image *newImage(const std::vector &data, const Image::Settings &settings) = 0; - virtual Image *newImage(const std::vector &cdata, const Image::Settings &settings) = 0; + virtual Image *newImage(const Image::Slices &data, const Image::Settings &settings) = 0; + virtual Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) = 0; Quad *newQuad(Quad::Viewport v, double sw, double sh); + Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter); + Video *newVideo(love::video::VideoStream *stream, float pixeldensity); virtual SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) = 0; virtual ParticleSystem *newParticleSystem(Texture *texture, int size) = 0; - virtual Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter) = 0; - - virtual Canvas *newCanvas(int width, int height, const Canvas::Settings &settings) = 0; - + virtual Canvas *newCanvas(const Canvas::Settings &settings) = 0; virtual Shader *newShader(const Shader::ShaderSource &source) = 0; - virtual Video *newVideo(love::video::VideoStream *stream, float pixeldensity) = 0; - virtual Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) = 0; virtual Mesh *newMesh(const std::vector &vertices, Mesh::DrawMode drawmode, vertex::Usage usage) = 0; @@ -435,12 +455,12 @@ public: Shader *getShader() const; - void setCanvas(Canvas *canvas); - virtual void setCanvas(const std::vector &canvases) = 0; - void setCanvas(const std::vector> &canvases); + void setCanvas(RenderTarget rt); + virtual void setCanvas(const std::vector &rts) = 0; + void setCanvas(const std::vector &rts); virtual void setCanvas() = 0; - std::vector getCanvas() const; + std::vector getCanvas() const; bool isCanvasActive() const; bool isCanvasActive(Canvas *canvas) const; @@ -790,7 +810,7 @@ protected: StrongRef font; StrongRef shader; - std::vector> canvases; + std::vector renderTargets; ColorMask colorMask = ColorMask(true, true, true, true); @@ -810,7 +830,6 @@ protected: vertex::PrimitiveMode primitiveMode = vertex::PrimitiveMode::TRIANGLES; vertex::CommonFormat formats[2]; StrongRef texture; - ptrdiff_t textureHandle = 0; int vertexCount = 0; int indexCount = 0; diff --git a/src/modules/graphics/Image.cpp b/src/modules/graphics/Image.cpp index 77b1e44ff..3bfa2265a 100644 --- a/src/modules/graphics/Image.cpp +++ b/src/modules/graphics/Image.cpp @@ -19,6 +19,10 @@ **/ #include "Image.h" +#include "Graphics.h" + +// C++ +#include namespace love { @@ -29,9 +33,16 @@ love::Type Image::type("Image", &Texture::type); int Image::imageCount = 0; -Image::Image(const Settings &settings) - : settings(settings) +Image::Image(const Slices &data, const Settings &settings, bool validatedata) + : Texture(data.getTextureType()) + , settings(settings) + , data(data) + , mipmapsType(settings.mipmaps ? MIPMAPS_GENERATED : MIPMAPS_NONE) + , sRGB(isGammaCorrect() && !settings.linear) { + if (validatedata && data.validate() == MIPMAPS_DATA) + mipmapsType = MIPMAPS_DATA; + ++imageCount; } @@ -40,29 +51,166 @@ Image::~Image() --imageCount; } -const Image::Settings &Image::getFlags() const +Image::Slices::Slices(TextureType textype) + : textureType(textype) { - return settings; } -bool Image::getConstant(const char *in, SettingType &out) +void Image::Slices::clear() { - return settingTypes.find(in, out); + data.clear(); } -bool Image::getConstant(SettingType in, const char *&out) +void Image::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d) { - return settingTypes.find(in, out); + if (textureType == TEXTURE_VOLUME) + { + if (mipmap >= (int) data.size()) + data.resize(mipmap + 1); + + if (slice >= (int) data[mipmap].size()) + data[mipmap].resize(slice + 1); + + data[mipmap][slice].set(d); + } + else + { + if (slice >= (int) data.size()) + data.resize(slice + 1); + + if (mipmap >= (int) data[slice].size()) + data[slice].resize(mipmap + 1); + + data[slice][mipmap].set(d); + } } -StringMap::Entry Image::settingTypeEntries[] = +love::image::ImageDataBase *Image::Slices::get(int slice, int mipmap) const { - { "mipmaps", SETTING_MIPMAPS }, - { "linear", SETTING_LINEAR }, - { "pixeldensity", SETTING_PIXELDENSITY }, -}; + if (slice < 0 || slice >= getSliceCount(mipmap)) + return nullptr; -StringMap Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries)); + if (mipmap < 0 || mipmap >= getMipmapCount(slice)) + return nullptr; + + if (textureType == TEXTURE_VOLUME) + return data[mipmap][slice].get(); + else + return data[slice][mipmap].get(); +} + +void Image::Slices::add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips) +{ + int slicecount = addallslices ? cdata->getSliceCount() : 1; + int mipcount = addallmips ? cdata->getMipmapCount() : 1; + + for (int mip = 0; mip < mipcount; mip++) + { + for (int slice = 0; slice < slicecount; slice++) + set(startslice + slice, startmip + mip, cdata->getSlice(slice, mip)); + } +} + +int Image::Slices::getSliceCount(int mip) const +{ + if (textureType == TEXTURE_VOLUME) + { + if (mip < 0 || mip >= (int) data.size()) + return 0; + + return (int) data[mip].size(); + } + else + return (int) data.size(); +} + +int Image::Slices::getMipmapCount(int slice) const +{ + if (textureType == TEXTURE_VOLUME) + return (int) data.size(); + else + { + if (slice < 0 || slice >= (int) data.size()) + return 0; + + return data[slice].size(); + } +} + +Image::MipmapsType Image::Slices::validate() const +{ + int slicecount = getSliceCount(); + int mipcount = getMipmapCount(0); + + if (slicecount == 0 || mipcount == 0) + throw love::Exception("At least one ImageData or CompressedImageData is required!"); + + if (textureType == TEXTURE_CUBE && slicecount != 6) + throw love::Exception("Cube textures must have exactly 6 sides."); + + image::ImageDataBase *firstdata = get(0, 0); + + int w = firstdata->getWidth(); + int h = firstdata->getHeight(); + PixelFormat format = firstdata->getFormat(); + + int expectedmips = Texture::getMipmapCount(w, h); + + if (mipcount != expectedmips && mipcount != 1) + throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount); + + if (textureType == TEXTURE_CUBE && w != h) + throw love::Exception("Cube images must have equal widths and heights for each cube face."); + + int mipw = w; + int miph = h; + int mipslices = slicecount; + + for (int mip = 0; mip < mipcount; mip++) + { + if (textureType == TEXTURE_VOLUME) + { + slicecount = getSliceCount(mip); + + if (slicecount != mipslices) + throw love::Exception("Invalid number of image data layers in mipmap level %d (expected %d, got %d)", mip+1, mipslices, slicecount); + } + + for (int slice = 0; slice < slicecount; slice++) + { + auto slicedata = get(slice, mip); + + if (slicedata == nullptr) + throw love::Exception("Missing image data (slice %d, mipmap level %d)", slice+1, mip+1); + + int realw = slicedata->getWidth(); + int realh = slicedata->getHeight(); + + if (getMipmapCount(slice) != mipcount) + throw love::Exception("All Image layers must have the same mipmap count."); + + if (mipw != realw) + throw love::Exception("Width of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, mipw, realw); + + if (miph != realh) + throw love::Exception("Height of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, miph, realh); + + if (format != slicedata->getFormat()) + throw love::Exception("All Image slices and mipmaps must have the same pixel format."); + } + + mipw = std::max(mipw / 2, 1); + miph = std::max(miph / 2, 1); + + if (textureType == TEXTURE_VOLUME) + mipslices = std::max(mipslices / 2, 1); + } + + if (mipcount > 1) + return MIPMAPS_DATA; + else + return MIPMAPS_NONE; +} } // graphics } // love diff --git a/src/modules/graphics/Image.h b/src/modules/graphics/Image.h index 81bcd7171..e15defec8 100644 --- a/src/modules/graphics/Image.h +++ b/src/modules/graphics/Image.h @@ -39,12 +39,11 @@ public: static love::Type type; - enum SettingType + enum MipmapsType { - SETTING_MIPMAPS, - SETTING_LINEAR, - SETTING_PIXELDENSITY, - SETTING_MAX_ENUM + MIPMAPS_NONE, + MIPMAPS_DATA, + MIPMAPS_GENERATED, }; struct Settings @@ -54,33 +53,59 @@ public: float pixeldensity = 1.0f; }; - Image(const Settings &settings); + struct Slices + { + public: + + Slices(TextureType textype); + + void clear(); + void set(int slice, int mipmap, love::image::ImageDataBase *data); + love::image::ImageDataBase *get(int slice, int mipmap) const; + + void add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips); + + int getSliceCount(int mip = 0) const; + int getMipmapCount(int slice = 0) const; + + MipmapsType validate() const; + + TextureType getTextureType() const { return textureType; } + + private: + + TextureType textureType; + + // For 2D/Cube/2DArray texture types, each element in the data array has + // an array of mipmap levels. For 3D texture types, each mipmap level + // has an array of layers. + std::vector>> data; + + }; // Slices + virtual ~Image(); - virtual const std::vector> &getImageData() const = 0; - virtual const std::vector> &getCompressedData() const = 0; - - virtual void setMipmapSharpness(float sharpness) = 0; - virtual float getMipmapSharpness() const = 0; + virtual void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) = 0; + virtual void replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) = 0; + virtual bool isFormatLinear() const = 0; virtual bool isCompressed() const = 0; - virtual bool refresh(int xoffset, int yoffset, int w, int h) = 0; - - const Settings &getFlags() const; - - static bool getConstant(const char *in, SettingType &out); - static bool getConstant(SettingType in, const char *&out); + virtual MipmapsType getMipmapsType() const = 0; static int imageCount; protected: + Image(const Slices &data, const Settings &settings, bool validatedata); + // The settings used to initialize this Image. Settings settings; - - static StringMap::Entry settingTypeEntries[]; - static StringMap settingTypes; + + Slices data; + + MipmapsType mipmapsType; + bool sRGB; }; // Image diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index 291d9fed5..d4751f475 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -174,6 +174,14 @@ void Shader::attachDefault() current = nullptr; } +void Shader::checkMainTextureType(TextureType textype) const +{ + const UniformInfo *info = getUniformInfo(BUILTIN_TEXTURE_MAIN); + + if (info != nullptr && info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype) + throw love::Exception("Texture's type must match the type of the shader's main texture."); +} + bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool checkWithDefaults, std::string &err) { if (source.vertex.empty() && source.pixel.empty()) diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index 66efe8744..1589ec5ff 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -119,6 +119,7 @@ public: }; UniformType baseType; + TextureType textureType; std::string name; union @@ -144,11 +145,8 @@ public: /** * Binds this Shader's program to be used when rendering. - * - * @param temporary True if we just want to send values to the shader with - * no intention of rendering. **/ - virtual void attach(bool temporary = false) = 0; + virtual void attach() = 0; /** * Attach the default shader. @@ -161,9 +159,11 @@ public: virtual std::string getWarnings() const = 0; virtual const UniformInfo *getUniformInfo(const std::string &name) const = 0; - virtual void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false) = 0; + virtual const UniformInfo *getUniformInfo(BuiltinUniform builtin) const = 0; - virtual void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false) = 0; + virtual void updateUniform(const UniformInfo *info, int count) = 0; + + virtual void sendTextures(const UniformInfo *info, Texture **textures, int count) = 0; /** * Gets whether a uniform with the specified name exists and is actively @@ -174,7 +174,9 @@ public: /** * Sets the textures used when rendering a video. For internal use only. **/ - virtual void setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) = 0; + virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0; + + void checkMainTextureType(TextureType textype) const; virtual ptrdiff_t getHandle() const = 0; diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index 30344261b..9bde3ab59 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -18,9 +18,25 @@ * 3. This notice may not be removed or altered from any source distribution. **/ +// LOVE +#include "common/config.h" #include "Texture.h" #include "Graphics.h" +// C +#include +#include + +#ifdef LOVE_ANDROID +// log2 is not declared in the math.h shipped with the Android NDK +static inline double log2(double n) +{ + // log(n)/log(2) is log2. + return std::log(n) / std::log(2); +} +#endif + + namespace love { namespace graphics @@ -32,14 +48,19 @@ Texture::Filter Texture::defaultFilter; Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR; float Texture::defaultMipmapSharpness = 0.0f; -Texture::Texture() - : format(PIXELFORMAT_UNKNOWN) +Texture::Texture(TextureType texType) + : texType(texType) + , format(PIXELFORMAT_UNKNOWN) , width(0) , height(0) + , depth(1) + , layers(1) + , mipmapCount(1) , pixelWidth(0) , pixelHeight(0) , filter(defaultFilter) , wrap() + , mipmapSharpness(defaultMipmapSharpness) , vertices() { } @@ -48,6 +69,39 @@ Texture::~Texture() { } +void Texture::initVertices() +{ + for (int i = 0; i < 4; i++) + vertices[i].color = Color(255, 255, 255, 255); + + // Vertices are ordered for use with triangle strips: + // 0---2 + // | / | + // 1---3 + vertices[0].x = 0.0f; + vertices[0].y = 0.0f; + vertices[1].x = 0.0f; + vertices[1].y = (float) height; + vertices[2].x = (float) width; + vertices[2].y = 0.0f; + vertices[3].x = (float) width; + vertices[3].y = (float) height; + + vertices[0].s = 0.0f; + vertices[0].t = 0.0f; + vertices[1].s = 0.0f; + vertices[1].t = 1.0f; + vertices[2].s = 1.0f; + vertices[2].t = 0.0f; + vertices[3].s = 1.0f; + vertices[3].t = 1.0f; +} + +TextureType Texture::getTextureType() const +{ + return texType; +} + PixelFormat Texture::getPixelFormat() const { return format; @@ -65,6 +119,9 @@ void Texture::drawq(Graphics *gfx, Quad *quad, const Matrix4 &m) void Texture::drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v) { + if (Shader::current) + Shader::current->checkMainTextureType(texType); + Matrix4 t(gfx->getTransform(), localTransform); Vertex verts[4] = {v[0], v[1], v[2], v[3]}; @@ -95,6 +152,21 @@ int Texture::getHeight() const return height; } +int Texture::getDepth() const +{ + return depth; +} + +int Texture::getLayerCount() const +{ + return layers; +} + +int Texture::getMipmapCount() const +{ + return mipmapCount; +} + int Texture::getPixelWidth() const { return pixelWidth; @@ -120,6 +192,11 @@ const Texture::Wrap &Texture::getWrap() const return wrap; } +float Texture::getMipmapSharpness() const +{ + return mipmapSharpness; +} + const Vertex *Texture::getVertices() const { return vertices; @@ -142,12 +219,32 @@ bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed) return true; } +int Texture::getMipmapCount(int w, int h) +{ + return (int) log2(std::max(w, h)) + 1; +} + +int Texture::getMipmapCount(int w, int h, int d) +{ + return (int) log2(std::max(std::max(w, h), d)) + 1; +} + +bool Texture::getConstant(const char *in, TextureType &out) +{ + return texTypes.find(in, out); +} + +bool Texture::getConstant(TextureType in, const char *&out) +{ + return texTypes.find(in, out); +} + bool Texture::getConstant(const char *in, FilterMode &out) { return filterModes.find(in, out); } -bool Texture::getConstant(FilterMode in, const char *&out) +bool Texture::getConstant(FilterMode in, const char *&out) { return filterModes.find(in, out); } @@ -157,11 +254,21 @@ bool Texture::getConstant(const char *in, WrapMode &out) return wrapModes.find(in, out); } -bool Texture::getConstant(WrapMode in, const char *&out) +bool Texture::getConstant(WrapMode in, const char *&out) { return wrapModes.find(in, out); } +StringMap::Entry Texture::texTypeEntries[] = +{ + { "2d", TEXTURE_2D }, + { "volume", TEXTURE_VOLUME }, + { "array", TEXTURE_2D_ARRAY }, + { "cube", TEXTURE_CUBE }, +}; + +StringMap Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries)); + StringMap::Entry Texture::filterModeEntries[] = { { "linear", FILTER_LINEAR }, diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index 61699293b..69a8b6c91 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -25,6 +25,7 @@ #include "common/StringMap.h" #include "common/math.h" #include "common/pixelformat.h" +#include "common/Exception.h" #include "Drawable.h" #include "Quad.h" #include "vertex.h" @@ -37,6 +38,25 @@ namespace love namespace graphics { +class Graphics; + +enum TextureType +{ + TEXTURE_2D, + TEXTURE_VOLUME, + TEXTURE_2D_ARRAY, + TEXTURE_CUBE, + TEXTURE_MAX_ENUM +}; + +class TextureTooLargeException : public love::Exception +{ +public: + TextureTooLargeException(const char *dimname, int pix) + : Exception("Cannot create texture: %s of %d pixels is too large for this system.", dimname, pix) + {} +}; + /** * Base class for 2D textures. All textures can be drawn with Quads, have a * width and height, and have filter and wrap modes. @@ -76,9 +96,10 @@ public: { WrapMode s = WRAP_CLAMP; WrapMode t = WRAP_CLAMP; + WrapMode r = WRAP_CLAMP; }; - Texture(); + Texture(TextureType texType); virtual ~Texture(); static Filter defaultFilter; @@ -93,10 +114,14 @@ public: **/ void drawq(Graphics *gfx, Quad *quad, const Matrix4 &m); + TextureType getTextureType() const; PixelFormat getPixelFormat() const; - virtual int getWidth() const; - virtual int getHeight() const; + int getWidth() const; + int getHeight() const; + int getDepth() const; + int getLayerCount() const; + int getMipmapCount() const; virtual int getPixelWidth() const; virtual int getPixelHeight() const; @@ -109,37 +134,59 @@ public: virtual bool setWrap(const Wrap &w) = 0; virtual const Wrap &getWrap() const; + // Sets the mipmap texture LOD bias (sharpness) value. + virtual bool setMipmapSharpness(float sharpness) = 0; + float getMipmapSharpness() const; + virtual const Vertex *getVertices() const; virtual ptrdiff_t getHandle() const = 0; static bool validateFilter(const Filter &f, bool mipmapsAllowed); + static int getMipmapCount(int w, int h); + static int getMipmapCount(int w, int h, int d); + + static bool getConstant(const char *in, TextureType &out); + static bool getConstant(TextureType in, const char *&out); + static bool getConstant(const char *in, FilterMode &out); - static bool getConstant(FilterMode in, const char *&out); + static bool getConstant(FilterMode in, const char *&out); static bool getConstant(const char *in, WrapMode &out); - static bool getConstant(WrapMode in, const char *&out); + static bool getConstant(WrapMode in, const char *&out); protected: + void initVertices(); virtual void drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v); + TextureType texType; + PixelFormat format; int width; int height; + int depth; + int layers; + int mipmapCount; int pixelWidth; int pixelHeight; + Filter filter; Wrap wrap; + float mipmapSharpness; + Vertex vertices[4]; private: + static StringMap::Entry texTypeEntries[]; + static StringMap texTypes; + static StringMap::Entry filterModeEntries[]; static StringMap filterModes; diff --git a/src/modules/graphics/Video.cpp b/src/modules/graphics/Video.cpp index 6c3d53dac..63ea5e35a 100644 --- a/src/modules/graphics/Video.cpp +++ b/src/modules/graphics/Video.cpp @@ -31,14 +31,12 @@ namespace graphics love::Type Video::type("Video", &Drawable::type); -Video::Video(love::video::VideoStream *stream, float pixeldensity) +Video::Video(Graphics *gfx, love::video::VideoStream *stream, float pixeldensity) : stream(stream) , width(stream->getWidth() / pixeldensity) , height(stream->getHeight() / pixeldensity) , filter(Texture::defaultFilter) { - textureHandles[2] = textureHandles[1] = textureHandles[0] = 0; - filter.mipmap = Texture::FILTER_NONE; stream->fillBackBuffer(); @@ -67,6 +65,33 @@ Video::Video(love::video::VideoStream *stream, float pixeldensity) vertices[2].t = 0.0f; vertices[3].s = 1.0f; vertices[3].t = 1.0f; + + // Create the textures using the initial frame data. + auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer(); + + int widths[3] = {frame->yw, frame->cw, frame->cw}; + int heights[3] = {frame->yh, frame->ch, frame->ch}; + + const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane}; + + Texture::Wrap wrap; // Clamp wrap mode. + Image::Settings settings; + + for (int i = 0; i < 3; i++) + { + Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8, widths[i], heights[i], 1, settings); + + img->setFilter(filter); + img->setWrap(wrap); + + size_t bpp = getPixelFormatSize(PIXELFORMAT_R8); + size_t size = bpp * widths[i] * heights[i]; + + Rect rect = {0, 0, widths[i], heights[i]}; + img->replacePixels(data[i], size, rect, 0, 0, false); + + images[i].set(img, Acquire::NORETAIN); + } } Video::~Video() @@ -93,7 +118,7 @@ void Video::draw(Graphics *gfx, const Matrix4 &m) shader = Shader::defaultVideoShader; } - shader->setVideoTextures(textureHandles[0], textureHandles[1], textureHandles[2]); + shader->setVideoTextures(images[0], images[1], images[2]); Graphics::StreamDrawRequest req; req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub; @@ -129,7 +154,20 @@ void Video::update() if (bufferschanged) { auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer(); - uploadFrame(frame); + + int widths[3] = {frame->yw, frame->cw, frame->cw}; + int heights[3] = {frame->yh, frame->ch, frame->ch}; + + const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane}; + + for (int i = 0; i < 3; i++) + { + size_t bpp = getPixelFormatSize(PIXELFORMAT_R8); + size_t size = bpp * widths[i] * heights[i]; + + Rect rect = {0, 0, widths[i], heights[i]}; + images[i]->replacePixels(data[i], size, rect, 0, 0, false); + } } } @@ -163,6 +201,14 @@ int Video::getPixelHeight() const return stream->getHeight(); } +void Video::setFilter(const Texture::Filter &f) +{ + for (const auto &image : images) + image->setFilter(f); + + filter = f; +} + const Texture::Filter &Video::getFilter() const { return filter; diff --git a/src/modules/graphics/Video.h b/src/modules/graphics/Video.h index d44eb60fa..ec9cecb26 100644 --- a/src/modules/graphics/Video.h +++ b/src/modules/graphics/Video.h @@ -23,7 +23,7 @@ // LOVE #include "common/math.h" #include "Drawable.h" -#include "Texture.h" +#include "Image.h" #include "vertex.h" #include "video/VideoStream.h" #include "audio/Source.h" @@ -33,13 +33,15 @@ namespace love namespace graphics { +class Graphics; + class Video : public Drawable { public: static love::Type type; - Video(love::video::VideoStream *stream, float pixeldensity = 1.0f); + Video(Graphics *gfx, love::video::VideoStream *stream, float pixeldensity = 1.0f); virtual ~Video(); // Drawable @@ -56,12 +58,12 @@ public: int getPixelWidth() const; int getPixelHeight() const; - virtual void setFilter(const Texture::Filter &f) = 0; + void setFilter(const Texture::Filter &f); const Texture::Filter &getFilter() const; -protected: +private: - virtual void uploadFrame(const love::video::VideoStream::Frame *frame) = 0; + void update(); StrongRef stream; @@ -72,11 +74,7 @@ protected: Vertex vertices[4]; - ptrdiff_t textureHandles[3]; - -private: - - void update(); + StrongRef images[3]; StrongRef source; }; // Video diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 37cf23e81..1d3fc955c 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -30,7 +30,7 @@ namespace graphics namespace opengl { -static GLenum createFBO(GLuint &framebuffer, GLuint texture) +static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture, int layers, bool initialize) { // get currently bound fbo to reset to it later GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); @@ -40,11 +40,27 @@ static GLenum createFBO(GLuint &framebuffer, GLuint texture) if (texture != 0) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + if (initialize) + { + int faces = texType == TEXTURE_CUBE ? 6 : 1; - // Initialize the texture to transparent black. - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); + // Make sure all faces and layers of the texture are initialized to + // transparent black. This is unfortunately probably pretty slow for + // 2D-array and 3D textures with a lot of layers... + for (int layer = layers - 1; layer >= 0; layer--) + { + for (int face = faces - 1; face >= 0; face--) + { + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + } + } + else + { + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0); + } } GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -96,46 +112,39 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi return status == GL_FRAMEBUFFER_COMPLETE && samples > 1; } -Canvas::Canvas(int width, int height, const Settings &settings) - : settings(settings) +Canvas::Canvas(const Settings &settings) + : love::graphics::Canvas(settings.type) , fbo(0) , texture(0) , msaa_buffer(0) , actual_samples(0) , texture_memory(0) { - this->width = width; - this->height = height; + this->width = settings.width; + this->height = settings.height; this->pixelWidth = (int) ((width * settings.pixeldensity) + 0.5); this->pixelHeight = (int) ((height * settings.pixeldensity) + 0.5); - // Vertices are ordered for use with triangle strips: - // 0---2 - // | / | - // 1---3 - // world coordinates - vertices[0].x = 0; - vertices[0].y = 0; - vertices[1].x = 0; - vertices[1].y = (float) height; - vertices[2].x = (float) width; - vertices[2].y = 0; - vertices[3].x = (float) width; - vertices[3].y = (float) height; + if (texType == TEXTURE_VOLUME) + this->depth = settings.layers; + else if (texType == TEXTURE_2D_ARRAY) + this->layers = settings.layers; + else + this->layers = 1; - // texture coordinates - vertices[0].s = 0; - vertices[0].t = 0; - vertices[1].s = 0; - vertices[1].t = 1; - vertices[2].s = 1; - vertices[2].t = 0; - vertices[3].s = 1; - vertices[3].t = 1; + if (width <= 0 || height <= 0 || layers <= 0) + throw love::Exception("Canvas dimensions must be greater than 0."); + + if (texType != TEXTURE_2D && settings.msaa > 1) + throw love::Exception("MSAA is only supported for Canvases with the 2D texture type."); this->format = getSizedFormat(settings.format); + initVertices(); loadVolatile(); + + if (status != GL_FRAMEBUFFER_COMPLETE) + throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(status)); } Canvas::~Canvas() @@ -148,41 +157,91 @@ bool Canvas::loadVolatile() if (texture != 0) return true; + if (!Canvas::isSupported()) + throw love::Exception("Canvases are not supported by your OpenGL drivers!"); + + if (!Canvas::isFormatSupported(format)) + { + const char *fstr = "rgba8"; + love::getConstant(Canvas::getSizedFormat(format), fstr); + throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr); + } + + if (settings.msaa > 1 && texType != TEXTURE_2D) + throw love::Exception("MSAA is only supported for 2D texture types."); + + if (!gl.isTextureTypeSupported(texType)) + { + const char *textypestr = "unknown"; + getConstant(texType, textypestr); + throw love::Exception("%s textures are not supported on this system!", textypestr); + } + + switch (texType) + { + case TEXTURE_2D: + if (pixelWidth > gl.getMax2DTextureSize()) + throw TextureTooLargeException("width", pixelWidth); + else if (pixelHeight > gl.getMax2DTextureSize()) + throw TextureTooLargeException("height", pixelHeight); + break; + case TEXTURE_VOLUME: + if (pixelWidth > gl.getMax3DTextureSize()) + throw TextureTooLargeException("width", pixelWidth); + else if (pixelHeight > gl.getMax3DTextureSize()) + throw TextureTooLargeException("height", pixelHeight); + else if (depth > gl.getMax3DTextureSize()) + throw TextureTooLargeException("depth", depth); + break; + case TEXTURE_2D_ARRAY: + if (pixelWidth > gl.getMax2DTextureSize()) + throw TextureTooLargeException("width", pixelWidth); + else if (pixelHeight > gl.getMax2DTextureSize()) + throw TextureTooLargeException("height", pixelHeight); + else if (layers > gl.getMaxTextureLayers()) + throw TextureTooLargeException("array layer count", layers); + break; + case TEXTURE_CUBE: + if (pixelWidth != pixelHeight) + throw love::Exception("Cubemap textures must have equal width and height."); + else if (pixelWidth > gl.getMaxCubeTextureSize()) + throw TextureTooLargeException("width", pixelWidth); + break; + default: + break; + } + OpenGL::TempDebugGroup debuggroup("Canvas load"); fbo = texture = 0; msaa_buffer = 0; status = GL_FRAMEBUFFER_COMPLETE; - // glTexImage2D is guaranteed to error in this case. - if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize()) - { - status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; - return false; - } - // getMaxRenderbufferSamples will be 0 on systems that don't support // multisampled renderbuffers / don't export FBO multisample extensions. settings.msaa = std::min(settings.msaa, gl.getMaxRenderbufferSamples()); settings.msaa = std::max(settings.msaa, 0); glGenTextures(1, &texture); - gl.bindTextureToUnit(texture, 0, false); + gl.bindTextureToUnit(this, 0, false); + + GLenum gltype = OpenGL::getGLTextureType(texType); if (GLAD_ANGLE_texture_usage) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); + glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); setFilter(filter); setWrap(wrap); - bool unusedSRGB = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB); - while (glGetError() != GL_NO_ERROR) /* Clear the error buffer. */; - glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, pixelWidth, pixelHeight, - 0, fmt.externalformat, fmt.type, nullptr); + bool isSRGB = format == PIXELFORMAT_sRGBA8; + if (!gl.rawTexStorage(texType, 1, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers)) + { + status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; + return false; + } if (glGetError() != GL_NO_ERROR) { @@ -193,7 +252,7 @@ bool Canvas::loadVolatile() } // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. - status = createFBO(fbo, texture); + status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true); if (status != GL_FRAMEBUFFER_COMPLETE) { @@ -207,7 +266,7 @@ bool Canvas::loadVolatile() actual_samples = settings.msaa == 1 ? 0 : settings.msaa; - if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, format, msaa_buffer)) + if (actual_samples > 0 && !createMSAABuffer(pixelWidth, pixelHeight, actual_samples, format, msaa_buffer)) actual_samples = 0; size_t prevmemsize = texture_memory; @@ -246,49 +305,66 @@ void Canvas::setFilter(const Texture::Filter &f) throw love::Exception("Invalid texture filter."); filter = f; - gl.bindTextureToUnit(texture, 0, false); - gl.setTextureFilter(filter); + gl.bindTextureToUnit(this, 0, false); + gl.setTextureFilter(texType, filter); } bool Canvas::setWrap(const Texture::Wrap &w) { bool success = true; + bool forceclamp = texType == TEXTURE_CUBE; wrap = w; + // If we only have limited NPOT support then the wrap mode must be CLAMP. if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot)) - && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight))) + && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth))) { - if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP) + forceclamp = true; + } + + if (forceclamp) + { + if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP) success = false; - // If we only have limited NPOT support then the wrap mode must be CLAMP. - wrap.s = wrap.t = WRAP_CLAMP; + wrap.s = wrap.t = wrap.r = WRAP_CLAMP; } if (!gl.isClampZeroTextureWrapSupported()) { - if (wrap.s == WRAP_CLAMP_ZERO) - wrap.s = WRAP_CLAMP; - if (wrap.t == WRAP_CLAMP_ZERO) - wrap.t = WRAP_CLAMP; + if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP; + if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP; + if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP; } - gl.bindTextureToUnit(texture, 0, false); - gl.setTextureWrap(wrap); + gl.bindTextureToUnit(this, 0, false); + gl.setTextureWrap(texType, wrap); return success; } +bool Canvas::setMipmapSharpness(float /*sharpness*/) +{ + return false; +} + ptrdiff_t Canvas::getHandle() const { return texture; } -love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h) +love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) { if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight()) throw love::Exception("Invalid rectangle dimensions."); + if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= depth) + || (texType == TEXTURE_2D_ARRAY && slice >= layers) + || (texType == TEXTURE_CUBE && slice >= 6)) + { + throw love::Exception("Invalid slice index."); + } + Graphics *gfx = Module::getInstance(Module::M_GRAPHICS); if (gfx != nullptr && gfx->isCanvasActive(this)) throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active."); @@ -323,8 +399,18 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO()); + if (slice > 0) + { + int layer = texType == TEXTURE_CUBE ? 0 : slice; + int face = texType == TEXTURE_CUBE ? slice : 0; + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face); + } + glReadPixels(x, y, w, h, fmt.externalformat, fmt.type, imagedata->getData()); + if (slice > 0) + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); return imagedata; @@ -383,14 +469,14 @@ bool Canvas::isFormatSupported(PixelFormat format) GLuint texture = 0; glGenTextures(1, &texture); - gl.bindTextureToUnit(texture, 0, false); + gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); Texture::Filter f; f.min = f.mag = Texture::FILTER_NEAREST; - gl.setTextureFilter(f); + gl.setTextureFilter(TEXTURE_2D, f); Texture::Wrap w; - gl.setTextureWrap(w); + gl.setTextureWrap(TEXTURE_2D, w); bool unusedSRGB = false; OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB); @@ -398,7 +484,7 @@ bool Canvas::isFormatSupported(PixelFormat format) glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, 2, 2, 0, fmt.externalformat, fmt.type, nullptr); GLuint fbo = 0; - supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE); + supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE); gl.deleteFramebuffer(fbo); gl.deleteTexture(texture); diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 6f7ec2e63..7647097ed 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -39,7 +39,7 @@ class Canvas final : public love::graphics::Canvas, public Volatile { public: - Canvas(int width, int height, const Settings &settings); + Canvas(const Settings &settings); virtual ~Canvas(); // Implements Volatile. @@ -49,9 +49,10 @@ public: // Implements Texture. void setFilter(const Texture::Filter &f) override; bool setWrap(const Texture::Wrap &w) override; + bool setMipmapSharpness(float sharpness) override; ptrdiff_t getHandle() const override; - love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h) override; + love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) override; int getMSAA() const override { diff --git a/src/modules/graphics/opengl/Font.cpp b/src/modules/graphics/opengl/Font.cpp deleted file mode 100644 index 0b090536b..000000000 --- a/src/modules/graphics/opengl/Font.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) 2006-2017 LOVE Development Team - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - **/ - -// LOVE -#include "Font.h" -#include "graphics/Graphics.h" - -namespace love -{ -namespace graphics -{ -namespace opengl -{ - -Font::Font(love::font::Rasterizer *r, const Texture::Filter &f) - : love::graphics::Font(r, f) - , textureMemorySize(0) -{ - loadVolatile(); -} - -Font::~Font() -{ - unloadVolatile(); -} - -void Font::createTexture() -{ - auto gfx = Module::getInstance(Module::M_GRAPHICS); - gfx->flushStreamDraws(); - - OpenGL::TempDebugGroup debuggroup("Font create texture"); - - size_t bpp = getPixelFormatSize(pixelFormat); - - size_t prevmemsize = textureMemorySize; - if (prevmemsize > 0) - { - textureMemorySize -= (textureWidth * textureHeight * bpp); - gl.updateTextureMemorySize(prevmemsize, textureMemorySize); - } - - GLuint t = 0; - TextureSize size = {textureWidth, textureHeight}; - TextureSize nextsize = getNextTextureSize(); - bool recreatetexture = false; - - // If we have an existing texture already, we'll try replacing it with a - // larger-sized one rather than creating a second one. Having a single - // texture reduces texture switches and draw calls when rendering. - if ((nextsize.width > size.width || nextsize.height > size.height) - && !textures.empty()) - { - recreatetexture = true; - size = nextsize; - t = textures.back(); - } - else - glGenTextures(1, &t); - - gl.bindTextureToUnit(t, 0, false); - - gl.setTextureFilter(filter); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - bool sRGB = isGammaCorrect(); - OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, sRGB); - - if (fmt.swizzled) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, fmt.swizzle[1]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, fmt.swizzle[2]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]); - } - - // Initialize the texture with transparent black. - std::vector emptydata(size.width * size.height * bpp, 0); - - // Clear errors before initializing. - while (glGetError() != GL_NO_ERROR); - - glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, size.width, size.height, - 0, fmt.externalformat, fmt.type, &emptydata[0]); - - if (glGetError() != GL_NO_ERROR) - { - if (!recreatetexture) - gl.deleteTexture(t); - throw love::Exception("Could not create font texture!"); - } - - textureWidth = size.width; - textureHeight = size.height; - - rowHeight = textureX = textureY = TEXTURE_PADDING; - - prevmemsize = textureMemorySize; - textureMemorySize += emptydata.size(); - gl.updateTextureMemorySize(prevmemsize, textureMemorySize); - - // Re-add the old glyphs if we re-created the existing texture object. - if (recreatetexture) - { - textureCacheID++; - - std::vector glyphstoadd; - - for (const auto &glyphpair : glyphs) - glyphstoadd.push_back(glyphpair.first); - - glyphs.clear(); - - for (uint32 g : glyphstoadd) - addGlyph(g); - } - else - textures.push_back(t); -} - -void Font::uploadGlyphToTexture(font::GlyphData *gd, Glyph &glyph) -{ - bool isSRGB = isGammaCorrect(); - OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, isSRGB); - - glyph.texture = textures.back(); - - gl.bindTextureToUnit(glyph.texture, 0, false); - glTexSubImage2D(GL_TEXTURE_2D, 0, textureX, textureY, gd->getWidth(), gd->getHeight(), - fmt.externalformat, fmt.type, gd->getData()); -} - -void Font::setFilter(const Texture::Filter &f) -{ - if (!Texture::validateFilter(f, false)) - throw love::Exception("Invalid texture filter."); - - filter = f; - - for (GLuint texture : textures) - { - gl.bindTextureToUnit(texture, 0, false); - gl.setTextureFilter(filter); - } -} - -bool Font::loadVolatile() -{ - createTexture(); - textureCacheID++; - return true; -} - -void Font::unloadVolatile() -{ - // nuke everything from orbit - - glyphs.clear(); - - for (GLuint texture : textures) - gl.deleteTexture(texture); - - textures.clear(); - - gl.updateTextureMemorySize(textureMemorySize, 0); - textureMemorySize = 0; -} - -} // opengl -} // graphics -} // love diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 418cc5185..f5c586ef7 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -25,12 +25,10 @@ #include "Graphics.h" #include "font/Font.h" -#include "Font.h" #include "StreamBuffer.h" #include "math/MathModule.h" #include "window/Window.h" #include "Buffer.h" -#include "Video.h" #include "Text.h" #include "libraries/xxHash/xxhash.h" @@ -103,19 +101,14 @@ love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t return CreateStreamBuffer(type, size); } -love::graphics::Image *Graphics::newImage(const std::vector &data, const Image::Settings &settings) +love::graphics::Image *Graphics::newImage(const Image::Slices &data, const Image::Settings &settings) { return new Image(data, settings); } -love::graphics::Image *Graphics::newImage(const std::vector &cdata, const Image::Settings &settings) +love::graphics::Image *Graphics::newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) { - return new Image(cdata, settings); -} - -graphics::Font *Graphics::newFont(love::font::Rasterizer *r, const Texture::Filter &filter) -{ - return new Font(r, filter); + return new Image(textype, format, width, height, slices, settings); } love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, vertex::Usage usage) @@ -128,35 +121,12 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in return new ParticleSystem(this, texture, size); } -love::graphics::Canvas *Graphics::newCanvas(int width, int height, const Canvas::Settings &settings) +love::graphics::Canvas *Graphics::newCanvas(const Canvas::Settings &settings) { - if (!Canvas::isSupported()) - throw love::Exception("Canvases are not supported by your OpenGL drivers!"); - - if (!Canvas::isFormatSupported(settings.format)) - { - const char *fstr = "rgba8"; - love::getConstant(Canvas::getSizedFormat(settings.format), fstr); - throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr); - } - - if (width > gl.getMaxTextureSize()) - throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width); - else if (height > gl.getMaxTextureSize()) - throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height); - - Canvas *canvas = new Canvas(width, height, settings); - GLenum err = canvas->getStatus(); - - // everything ok, return canvas (early out) - if (err == GL_FRAMEBUFFER_COMPLETE) - return canvas; - - canvas->release(); - throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(err)); - return nullptr; // never reached + return new Canvas(settings); } + love::graphics::Shader *Graphics::newShader(const Shader::ShaderSource &source) { return new Shader(source); @@ -192,11 +162,6 @@ love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector< return new Text(this, font, text); } -love::graphics::Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity) -{ - return new Video(stream, pixeldensity); -} - void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight) { this->width = width; @@ -204,7 +169,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh this->pixelWidth = pixelwidth; this->pixelHeight = pixelheight; - if (states.back().canvases.empty()) + if (states.back().renderTargets.empty()) { // Set the viewport to top-left corner. gl.setViewport({0, 0, pixelwidth, pixelheight}); @@ -262,6 +227,10 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b // Set pixel row alignment glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + // Always enable seamless cubemap filtering when possible. + if (GLAD_VERSION_3_2 || GLAD_ARB_seamless_cube_map) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + // Set whether drawing converts input from linear -> sRGB colorspace. if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB || GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB) @@ -374,6 +343,9 @@ void Graphics::flushStreamDraws() if (sbstate.vertexCount == 0 && sbstate.indexCount == 0) return; + if (Shader::current && sbstate.texture.get()) + Shader::current->checkMainTextureType(sbstate.texture->getTextureType()); + OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw"); uint32 attribs = 0; @@ -448,11 +420,7 @@ void Graphics::flushStreamDraws() pushIdentityTransform(); gl.prepareDraw(); - - if (sbstate.textureHandle != 0) - gl.bindTextureToUnit((GLuint) sbstate.textureHandle, 0, false); - else - gl.bindTextureToUnit(sbstate.texture, 0, false); + gl.bindTextureToUnit(sbstate.texture, 0, false); gl.useVertexAttribArrays(attribs); @@ -547,21 +515,22 @@ void Graphics::setDebug(bool enable) ::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n"); } -void Graphics::setCanvas(const std::vector &canvases) +void Graphics::setCanvas(const std::vector &rts) { DisplayState &state = states.back(); - int ncanvases = (int) canvases.size(); + int ncanvases = (int) rts.size(); if (ncanvases == 0) return setCanvas(); - if (ncanvases == (int) state.canvases.size()) + if (ncanvases == (int) state.renderTargets.size()) { bool modified = false; for (int i = 0; i < ncanvases; i++) { - if (canvases[i] != state.canvases[i].get()) + if (rts[i].canvas != state.renderTargets[i].canvas.get() + || rts[i].slice != state.renderTargets[i].slice) { modified = true; break; @@ -575,7 +544,7 @@ void Graphics::setCanvas(const std::vector &canvases) if (ncanvases > gl.getMaxRenderTargets()) throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases); - love::graphics::Canvas *firstcanvas = canvases[0]; + love::graphics::Canvas *firstcanvas = rts[0].canvas; bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported(); PixelFormat firstformat = firstcanvas->getPixelFormat(); @@ -586,7 +555,7 @@ void Graphics::setCanvas(const std::vector &canvases) for (int i = 1; i < ncanvases; i++) { - love::graphics::Canvas *c = canvases[i]; + love::graphics::Canvas *c = rts[i].canvas; if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight) throw love::Exception("All canvases in must have the same pixel dimensions."); @@ -595,7 +564,7 @@ void Graphics::setCanvas(const std::vector &canvases) throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) - throw love::Exception("All Canvases in must have the same requested MSAA value."); + throw love::Exception("All Canvases in must have the same MSAA value."); if (c->getPixelFormat() == PIXELFORMAT_sRGBA8) hasSRGBcanvas = true; @@ -605,7 +574,7 @@ void Graphics::setCanvas(const std::vector &canvases) endPass(); - bindCachedFBO(canvases); + bindCachedFBO(rts); gl.setViewport({0, 0, pixelwidth, pixelheight}); @@ -627,13 +596,13 @@ void Graphics::setCanvas(const std::vector &canvases) gl.setFramebufferSRGB(false); } - std::vector> canvasrefs; - canvasrefs.reserve(canvases.size()); + std::vector canvasrefs; + canvasrefs.reserve(rts.size()); - for (love::graphics::Canvas *c : canvases) - canvasrefs.push_back(c); + for (auto c : rts) + canvasrefs.emplace_back(c.canvas, c.slice); - std::swap(state.canvases, canvasrefs); + std::swap(state.renderTargets, canvasrefs); canvasSwitchCount++; } @@ -642,14 +611,14 @@ void Graphics::setCanvas() { DisplayState &state = states.back(); - if (state.canvases.empty()) + if (state.renderTargets.empty()) return; OpenGL::TempDebugGroup debuggroup("setCanvas()"); endPass(); - state.canvases.clear(); + state.renderTargets.clear(); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); @@ -682,17 +651,18 @@ void Graphics::endPass() // Discard the stencil buffer. discard({}, true); - auto &canvases = states.back().canvases; + auto &canvases = states.back().renderTargets; - // Resolve MSAA buffers. - if (canvases.size() > 0 && canvases[0]->getMSAA() > 1) + // Resolve MSAA buffers. MSAA is only supported for 2D render targets so we + // don't have to worry about resolving to slices. + if (canvases.size() > 0 && canvases[0].canvas->getMSAA() > 1) { - int w = canvases[0]->getPixelWidth(); - int h = canvases[0]->getPixelHeight(); + int w = canvases[0].canvas->getPixelWidth(); + int h = canvases[0].canvas->getPixelHeight(); for (int i = 0; i < (int) canvases.size(); i++) { - Canvas *c = (Canvas *) canvases[i].get(); + Canvas *c = (Canvas *) canvases[i].canvas.get(); glReadBuffer(GL_COLOR_ATTACHMENT0 + i); @@ -728,7 +698,7 @@ void Graphics::clear(const std::vector &colors) if (colors.size() == 0) return; - int ncanvases = (int) states.back().canvases.size(); + int ncanvases = (int) states.back().renderTargets.size(); int ncolors = std::min((int) colors.size(), ncanvases); if (ncolors <= 1 && ncanvases <= 1) @@ -810,7 +780,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (states.back().canvases.empty() && gl.getDefaultFBO() == 0) + if (states.back().renderTargets.empty() && gl.getDefaultFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); @@ -823,7 +793,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector } else { - int rendertargetcount = std::max((int) states.back().canvases.size(), 1); + int rendertargetcount = std::max((int) states.back().renderTargets.size(), 1); for (int i = 0; i < (int) colorbuffers.size(); i++) { @@ -845,11 +815,10 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]); } -void Graphics::bindCachedFBO(const std::vector &canvases) +void Graphics::bindCachedFBO(const std::vector &targets) { - int ncanvases = (int) canvases.size(); - - uint32 hash = XXH32(&canvases[0], sizeof(love::graphics::Canvas *) * ncanvases, 0); + int ntargets = (int) targets.size(); + uint32 hash = XXH32(&targets[0], sizeof(RenderTarget) * ntargets, 0); GLuint fbo = framebufferObjects[hash]; @@ -859,35 +828,40 @@ void Graphics::bindCachedFBO(const std::vector &canvas } else { - int w = canvases[0]->getPixelWidth(); - int h = canvases[0]->getPixelHeight(); - int msaa = std::max(canvases[0]->getMSAA(), 1); + int w = targets[0].canvas->getPixelWidth(); + int h = targets[0].canvas->getPixelHeight(); + int msaa = std::max(targets[0].canvas->getMSAA(), 1); glGenFramebuffers(1, &fbo); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS]; - for (int i = 0; i < ncanvases; i++) + for (int i = 0; i < ntargets; i++) { drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i; if (msaa > 1) { - GLuint rbo = (GLuint) canvases[i]->getMSAAHandle(); + GLuint rbo = (GLuint) targets[i].canvas->getMSAAHandle(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo); } else { - GLuint tex = (GLuint) canvases[i]->getHandle(); - glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0); + GLuint tex = (GLuint) targets[i].canvas->getHandle(); + TextureType textype = targets[i].canvas->getTextureType(); + + int layer = textype == TEXTURE_CUBE ? 0 : targets[i].slice; + int face = textype == TEXTURE_CUBE ? targets[i].slice : 0; + + gl.framebufferTexture(drawbuffers[i], textype, tex, 0, layer, face); } } - if (ncanvases > 1) - glDrawBuffers(ncanvases, drawbuffers); + if (ntargets > 1) + glDrawBuffers(ntargets, drawbuffers); - GLuint stencil = attachCachedStencilBuffer(w, h, canvases[0]->getRequestedMSAA()); + GLuint stencil = attachCachedStencilBuffer(w, h, targets[0].canvas->getRequestedMSAA()); if (stencil == 0) { @@ -904,7 +878,7 @@ void Graphics::bindCachedFBO(const std::vector &canvas const char *sstr = OpenGL::framebufferStatusString(status); throw love::Exception("Could not create Framebuffer Object! %s", sstr); } - + framebufferObjects[hash] = fbo; } } @@ -990,7 +964,7 @@ void Graphics::present(void *screenshotCallbackData) if (!isActive()) return; - if (!states.back().canvases.empty()) + if (!states.back().renderTargets.empty()) throw love::Exception("present cannot be called while a Canvas is active."); endPass(); @@ -1029,8 +1003,8 @@ void Graphics::present(void *screenshotCallbackData) { gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer); - // We need to do an explicit MSAA resolve on iOS, because it uses GLES - // FBOs rather than a system framebuffer. + // We need to do an explicit MSAA resolve on iOS, because it uses + // GLES FBOs rather than a system framebuffer. if (GLAD_ES_VERSION_3_0) glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); else if (GLAD_APPLE_framebuffer_multisample) @@ -1122,7 +1096,7 @@ void Graphics::setScissor(const Rect &rect) glrect.h = (int) (rect.h * density); // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor. - gl.setScissor(glrect, !state.canvases.empty()); + gl.setScissor(glrect, !state.renderTargets.empty()); state.scissor = true; state.scissorRect = rect; @@ -1139,7 +1113,7 @@ void Graphics::setScissor() void Graphics::drawToStencilBuffer(StencilAction action, int value) { - if (states.back().canvases.empty() && !windowHasStencil) + if (states.back().renderTargets.empty() && !windowHasStencil) throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer."); flushStreamDraws(); @@ -1200,7 +1174,7 @@ void Graphics::stopDrawToStencilBuffer() void Graphics::setStencilTest(CompareMode compare, int value) { - if (compare != COMPARE_ALWAYS && states.back().canvases.empty() && !windowHasStencil) + if (compare != COMPARE_ALWAYS && states.back().renderTargets.empty() && !windowHasStencil) throw love::Exception("The window must have stenciling enabled to use setStencilTest on the main screen."); DisplayState &state = states.back(); @@ -1454,15 +1428,21 @@ double Graphics::getSystemLimit(SystemLimit limittype) const { switch (limittype) { - case Graphics::LIMIT_POINT_SIZE: + case LIMIT_POINT_SIZE: return (double) gl.getMaxPointSize(); - case Graphics::LIMIT_TEXTURE_SIZE: - return (double) gl.getMaxTextureSize(); - case Graphics::LIMIT_MULTI_CANVAS: + case LIMIT_TEXTURE_SIZE: + return (double) gl.getMax2DTextureSize(); + case LIMIT_TEXTURE_LAYERS: + return (double) gl.getMaxTextureLayers(); + case LIMIT_VOLUME_TEXTURE_SIZE: + return (double) gl.getMax3DTextureSize(); + case LIMIT_CUBE_TEXTURE_SIZE: + return (double) gl.getMaxCubeTextureSize(); + case LIMIT_MULTI_CANVAS: return (double) gl.getMaxRenderTargets(); - case Graphics::LIMIT_CANVAS_MSAA: + case LIMIT_CANVAS_MSAA: return (double) gl.getMaxRenderbufferSamples(); - case Graphics::LIMIT_ANISOTROPY: + case LIMIT_ANISOTROPY: return (double) gl.getMaxAnisotropy(); default: return 0.0; @@ -1483,6 +1463,10 @@ bool Graphics::isSupported(Feature feature) const return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot; case FEATURE_PIXEL_SHADER_HIGHP: return gl.isPixelShaderHighpSupported(); + case FEATURE_ARRAY_TEXTURE: + return gl.isTextureTypeSupported(TEXTURE_2D_ARRAY); + case FEATURE_VOLUME_TEXTURE: + return gl.isTextureTypeSupported(TEXTURE_VOLUME); case FEATURE_GLSL3: return GLAD_ES_VERSION_3_0 || gl.isCoreProfile(); case FEATURE_INSTANCING: diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 159c811c4..aec148df3 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -61,17 +61,13 @@ public: // Implements Module. const char *getName() const override; - love::graphics::Image *newImage(const std::vector &data, const Image::Settings &settings) override; - love::graphics::Image *newImage(const std::vector &cdata, const Image::Settings &settings) override; - - love::graphics::Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter) override; + love::graphics::Image *newImage(const Image::Slices &data, const Image::Settings &settings) override; + love::graphics::Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) override; love::graphics::SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) override; - love::graphics::ParticleSystem *newParticleSystem(Texture *texture, int size) override; - love::graphics::Canvas *newCanvas(int width, int height, const Canvas::Settings &settings) override; - + love::graphics::Canvas *newCanvas(const Canvas::Settings &settings) override; love::graphics::Shader *newShader(const Shader::ShaderSource &source) override; love::graphics::Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) override; @@ -84,8 +80,6 @@ public: love::graphics::Text *newText(love::graphics::Font *font, const std::vector &text = {}) override; - love::graphics::Video *newVideo(love::video::VideoStream *stream, float pixeldensity) override; - void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override; bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) override; void unSetMode() override; @@ -103,7 +97,7 @@ public: void setColor(Colorf c) override; - void setCanvas(const std::vector &canvases) override; + void setCanvas(const std::vector &rts) override; void setCanvas() override; void setScissor(const Rect &rect) override; @@ -149,7 +143,7 @@ private: love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override; void endPass(); - void bindCachedFBO(const std::vector &canvases); + void bindCachedFBO(const std::vector &targets); void discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil); GLuint attachCachedStencilBuffer(int w, int h, int samples); diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index b4aee8220..ab56fdae5 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -26,16 +26,6 @@ // STD #include // for min/max -#ifdef LOVE_ANDROID -// log2 is not declared in the math.h shipped with the Android NDK -#include -inline double log2(double n) -{ - // log(n)/log(2) is log2. - return std::log(n) / std::log(2); -} -#endif - namespace love { namespace graphics @@ -45,114 +35,38 @@ namespace opengl float Image::maxMipmapSharpness = 0.0f; -static int getMipmapCount(int basewidth, int baseheight) -{ - return (int) log2(std::max(basewidth, baseheight)) + 1; -} - -template -static bool verifyMipmapLevels(const std::vector &miplevels) -{ - int numlevels = (int) miplevels.size(); - - if (numlevels == 1) - return false; - - int width = miplevels[0]->getWidth(); - int height = miplevels[0]->getHeight(); - - auto format = miplevels[0]->getFormat(); - - int expectedlevels = getMipmapCount(width, height); - - // All mip levels must be present when not using auto-generated mipmaps. - if (numlevels != expectedlevels) - throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedlevels, numlevels); - - // Verify the size of each mip level. - for (int i = 1; i < numlevels; i++) - { - width = std::max(width / 2, 1); - height = std::max(height / 2, 1); - - if (miplevels[i]->getWidth() != width) - throw love::Exception("Width of image mipmap level %d is incorrect (expected %d, got %d)", i+1, width, miplevels[i]->getWidth()); - if (miplevels[i]->getHeight() != height) - throw love::Exception("Height of image mipmap level %d is incorrect (expected %d, got %d)", i+1, height, miplevels[i]->getHeight()); - - if (miplevels[i]->getFormat() != format) - throw love::Exception("All image mipmap levels must have the same format."); - } - - return true; -} - -Image::Image(const std::vector &imagedata, const Settings &settings) - : love::graphics::Image(settings) +Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings) + : love::graphics::Image(Slices(textype), settings, false) , texture(0) - , mipmapSharpness(defaultMipmapSharpness) , compressed(false) - , sRGB(false) , usingDefaultTexture(false) , textureMemorySize(0) { - if (imagedata.empty()) - throw love::Exception(""); + if (isPixelFormatCompressed(format)) + throw love::Exception("This constructor is only supported for non-compressed pixel formats."); - pixelWidth = imagedata[0]->getWidth(); - pixelHeight = imagedata[0]->getHeight(); + if (textype == TEXTURE_VOLUME) + depth = slices; + else if (textype == TEXTURE_2D_ARRAY) + layers = slices; - width = (int) (pixelWidth / settings.pixeldensity + 0.5); - height = (int) (pixelHeight / settings.pixeldensity + 0.5); - - if (verifyMipmapLevels(imagedata)) - this->settings.mipmaps = true; - - for (const auto &id : imagedata) - data.push_back(id); - - format = data[0]->getFormat(); - - preload(); - loadVolatile(); + init(format, width, height, settings); } -Image::Image(const std::vector &compresseddata, const Settings &settings) - : love::graphics::Image(settings) +Image::Image(const Slices &slices, const Settings &settings) + : love::graphics::Image(slices, settings, true) , texture(0) - , mipmapSharpness(defaultMipmapSharpness) - , compressed(true) - , sRGB(false) + , compressed(false) , usingDefaultTexture(false) , textureMemorySize(0) { - pixelWidth = compresseddata[0]->getWidth(0); - pixelHeight = compresseddata[0]->getHeight(0); + if (texType == TEXTURE_2D_ARRAY) + this->layers = data.getSliceCount(); + else if (texType == TEXTURE_VOLUME) + this->depth = data.getSliceCount(); - width = (int) (pixelWidth / settings.pixeldensity + 0.5); - height = (int) (pixelHeight / settings.pixeldensity + 0.5); - - if (verifyMipmapLevels(compresseddata)) - this->settings.mipmaps = true; - else if (settings.mipmaps && getMipmapCount(pixelWidth, pixelHeight) != compresseddata[0]->getMipmapCount()) - { - if (compresseddata[0]->getMipmapCount() == 1) - this->settings.mipmaps = false; - else - { - throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels (expected %d, got %d)", - getMipmapCount(width, height), - compresseddata[0]->getMipmapCount()); - } - } - - for (image::CompressedImageData *cd : compresseddata) - cdata.push_back(cd); - - format = cdata[0]->getFormat(); - - preload(); - loadVolatile(); + love::image::ImageDataBase *slice = data.get(0, 0); + init(slice->getFormat(), slice->getWidth(), slice->getHeight(), settings); } Image::~Image() @@ -160,56 +74,41 @@ Image::~Image() unloadVolatile(); } -void Image::preload() +void Image::init(PixelFormat fmt, int w, int h, const Settings &settings) { - for (int i = 0; i < 4; i++) - vertices[i].color = Color(255, 255, 255, 255); + pixelWidth = w; + pixelHeight = h; - // Vertices are ordered for use with triangle strips: - // 0---2 - // | / | - // 1---3 - vertices[0].x = 0.0f; - vertices[0].y = 0.0f; - vertices[1].x = 0.0f; - vertices[1].y = (float) height; - vertices[2].x = (float) width; - vertices[2].y = 0.0f; - vertices[3].x = (float) width; - vertices[3].y = (float) height; + width = (int) (pixelWidth / settings.pixeldensity + 0.5); + height = (int) (pixelHeight / settings.pixeldensity + 0.5); - vertices[0].s = 0.0f; - vertices[0].t = 0.0f; - vertices[1].s = 0.0f; - vertices[1].t = 1.0f; - vertices[2].s = 1.0f; - vertices[2].t = 0.0f; - vertices[3].s = 1.0f; - vertices[3].t = 1.0f; + mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getMipmapCount(w, h); + format = fmt; + compressed = isPixelFormatCompressed(format); - if (settings.mipmaps) + if (compressed && mipmapsType == MIPMAPS_GENERATED) + mipmapsType = MIPMAPS_NONE; + + if (getMipmapCount() > 1) filter.mipmap = defaultMipmapFilter; - if (!isGammaCorrect()) - settings.linear = false; - - if (isGammaCorrect() && !settings.linear) - sRGB = true; - else - sRGB = false; + loadVolatile(); + initVertices(); } void Image::generateMipmaps() { // The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't // have support for glGenerateMipmap. - if (settings.mipmaps && !isCompressed() && - (GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)) + if (getMipmapCount() > 1 && !isCompressed() && + (GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object)) { - if (gl.bugs.generateMipmapsRequiresTexture2DEnable) - glEnable(GL_TEXTURE_2D); + GLenum gltextype = OpenGL::getGLTextureType(texType); - glGenerateMipmap(GL_TEXTURE_2D); + if (gl.bugs.generateMipmapsRequiresTexture2DEnable) + glEnable(gltextype); + + glGenerateMipmap(gltextype); } } @@ -217,65 +116,120 @@ void Image::loadDefaultTexture() { usingDefaultTexture = true; - gl.bindTextureToUnit(texture, 0, false); + gl.bindTextureToUnit(this, 0, false); setFilter(filter); + bool isSRGB = false; + gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8, isSRGB, 2, 2, 1); + // A nice friendly checkerboard to signify invalid textures... GLubyte px[] = {0xFF,0xFF,0xFF,0xFF, 0xFF,0xA0,0xA0,0xFF, 0xFF,0xA0,0xA0,0xFF, 0xFF,0xFF,0xFF,0xFF}; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, px); + int slices = texType == TEXTURE_CUBE ? 6 : 1; + Rect rect = {0, 0, 2, 2}; + for (int slice = 0; slice < slices; slice++) + uploadByteData(PIXELFORMAT_RGBA8, px, sizeof(px), rect, 0, slice); } -void Image::loadFromCompressedData() +void Image::loadData() { - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB); + int mipcount = getMipmapCount(); + int slicecount = 1; - if (isGammaCorrect() && !sRGB) - settings.linear = true; + if (texType == TEXTURE_VOLUME) + slicecount = getDepth(); + else if (texType == TEXTURE_2D_ARRAY) + slicecount = getLayerCount(); + else if (texType == TEXTURE_CUBE) + slicecount = 6; - int count = 1; + if (!isCompressed()) + gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers); - if (settings.mipmaps && cdata.size() > 1) - count = (int) cdata.size(); - else if (settings.mipmaps) - count = cdata[0]->getMipmapCount(); + if (mipmapsType == MIPMAPS_GENERATED) + mipcount = 1; - for (int i = 0; i < count; i++) + int w = pixelWidth; + int h = pixelHeight; + int d = depth; + + OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, sRGB); + + for (int mip = 0; mip < mipcount; mip++) { - // Compressed image mipmaps can come from separate CompressedImageData - // objects, or all from a single object. - auto cd = cdata.size() > 1 ? cdata[i].get() : cdata[0].get(); - int datamip = cdata.size() > 1 ? 0 : i; + if (isCompressed() && (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)) + { + size_t mipsize = 0; - glCompressedTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat, - cd->getWidth(datamip), cd->getHeight(datamip), 0, - (GLsizei) cd->getSize(datamip), cd->getData(datamip)); - } -} + if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME) + { + for (int slice = 0; slice < data.getSliceCount(mip); slice++) + mipsize += data.get(slice, mip)->getSize(); + } -void Image::loadFromImageData() -{ - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB); + GLenum gltarget = OpenGL::getGLTextureType(texType); + glCompressedTexImage3D(gltarget, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr); + } - if (isGammaCorrect() && !sRGB) - settings.linear = true; + for (int slice = 0; slice < slicecount; slice++) + { + love::image::ImageDataBase *id = data.get(slice, mip); - int mipcount = settings.mipmaps ? (int) data.size() : 1; + if (id != nullptr) + uploadImageData(id, mip, slice); + } - for (int i = 0; i < mipcount; i++) - { - love::image::ImageData *id = data[i].get(); - love::thread::Lock lock(id->getMutex()); + w = std::max(w / 2, 1); + h = std::max(h / 2, 1); - glTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat, id->getWidth(), id->getHeight(), - 0, fmt.externalformat, fmt.type, id->getData()); + if (texType == TEXTURE_VOLUME) + d = std::max(d / 2, 1); } - if (data.size() <= 1) + if (mipmapsType == MIPMAPS_GENERATED) generateMipmaps(); } +void Image::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, const Rect &r, int level, int slice) +{ + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, false, sRGB); + GLenum gltarget = OpenGL::getGLTextureType(texType); + + if (texType == TEXTURE_CUBE) + gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice; + + if (isPixelFormatCompressed(pixelformat)) + { + if (r.x != 0 || r.y != 0) + throw love::Exception("x and y parameters must be 0 for compressed images."); + + if (texType == TEXTURE_2D || texType == TEXTURE_CUBE) + glCompressedTexImage2D(gltarget, level, fmt.internalformat, r.w, r.h, 0, size, data); + else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME) + glCompressedTexSubImage3D(gltarget, level, 0, 0, slice, r.w, r.h, 1, fmt.internalformat, size, data); + } + else + { + if (texType == TEXTURE_2D || texType == TEXTURE_CUBE) + glTexSubImage2D(gltarget, level, r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data); + else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME) + glTexSubImage3D(gltarget, level, r.x, r.y, slice, r.w, r.h, 1, fmt.externalformat, fmt.type, data); + } +} + +void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice) +{ + love::image::ImageData *id = dynamic_cast(d); + + love::thread::EmptyLock lock; + if (id != nullptr) + lock.setLock(id->getMutex()); + + Rect rect = {0, 0, d->getWidth(), d->getHeight()}; + uploadByteData(d->getFormat(), d->getData(), d->getSize(), rect, level, slice); +} + bool Image::loadVolatile() { if (texture != 0) @@ -301,9 +255,9 @@ bool Image::loadVolatile() // GL_EXT_sRGB doesn't support glGenerateMipmap for sRGB textures. if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0) - && data.size() <= 1) + && mipmapsType != MIPMAPS_DATA) { - settings.mipmaps = false; + mipmapsType = MIPMAPS_NONE; filter.mipmap = FILTER_NONE; } } @@ -312,7 +266,7 @@ bool Image::loadVolatile() if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot)) && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight))) { - settings.mipmaps = false; + mipmapsType = MIPMAPS_NONE; filter.mipmap = FILTER_NONE; } @@ -320,38 +274,43 @@ bool Image::loadVolatile() glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxMipmapSharpness); glGenTextures(1, &texture); - gl.bindTextureToUnit(texture, 0, false); + gl.bindTextureToUnit(this, 0, false); - setFilter(filter); - setWrap(wrap); - setMipmapSharpness(mipmapSharpness); + bool loaddefault = false; + + int max2Dsize = gl.getMax2DTextureSize(); + int max3Dsize = gl.getMax3DTextureSize(); + + if ((texType == TEXTURE_2D || texType == TEXTURE_2D_ARRAY) && (pixelWidth > max2Dsize || pixelHeight > max2Dsize)) + loaddefault = true; + else if (texType == TEXTURE_2D_ARRAY && layers > gl.getMaxTextureLayers()) + loaddefault = true; + else if (texType == TEXTURE_CUBE && (pixelWidth > gl.getMaxCubeTextureSize() || pixelWidth != pixelHeight)) + loaddefault = true; + else if (texType == TEXTURE_VOLUME && (pixelWidth > max3Dsize || pixelHeight > max3Dsize || depth > max3Dsize)) + loaddefault = true; // Use a default texture if the size is too big for the system. - if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize()) + if (loaddefault) { loadDefaultTexture(); return true; } - if (!settings.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0)) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + setFilter(filter); + setWrap(wrap); + setMipmapSharpness(mipmapSharpness); - if (settings.mipmaps && !isCompressed() && data.size() <= 1 && - !(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)) - { - // Auto-generate mipmaps every time the texture is modified, if - // glGenerateMipmap isn't supported. - glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); - } + GLenum gltextype = OpenGL::getGLTextureType(texType); + + if (mipmapsType == MIPMAPS_NONE && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0)) + glTexParameteri(gltextype, GL_TEXTURE_MAX_LEVEL, 0); while (glGetError() != GL_NO_ERROR); // Clear errors. try { - if (isCompressed()) - loadFromCompressedData(); - else - loadFromImageData(); + loadData(); GLenum glerr = glGetError(); if (glerr != GL_NO_ERROR) @@ -365,13 +324,12 @@ bool Image::loadVolatile() } size_t prevmemsize = textureMemorySize; + textureMemorySize = 0; - if (isCompressed()) - textureMemorySize = cdata[0]->getSize(); - else - textureMemorySize = data[0]->getSize(); + for (int slice = 0; slice < data.getSliceCount(0); slice++) + textureMemorySize += data.get(slice, 0)->getSize(); - if (settings.mipmaps) + if (getMipmapCount() > 1) textureMemorySize *= 1.33334; gl.updateTextureMemorySize(prevmemsize, textureMemorySize); @@ -392,53 +350,61 @@ void Image::unloadVolatile() textureMemorySize = 0; } -bool Image::refresh(int xoffset, int yoffset, int w, int h) +void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) { // No effect if the texture hasn't been created yet. if (texture == 0 || usingDefaultTexture) - return false; + return; - if (xoffset < 0 || yoffset < 0 || w <= 0 || h <= 0 || - (xoffset + w) > pixelWidth || (yoffset + h) > pixelHeight) + if (d->getFormat() != getPixelFormat()) + throw love::Exception("Pixel formats must match."); + + if (mipmap < 0 || (mipmapsType != MIPMAPS_DATA && mipmap > 0) || mipmap >= getMipmapCount()) + throw love::Exception("Invalid image mipmap index."); + + if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6) + || (texType == TEXTURE_VOLUME && slice >= std::max(getDepth() >> mipmap, 1)) + || (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount())) { - throw love::Exception("Invalid rectangle dimensions."); + throw love::Exception("Invalid image slice index."); } - OpenGL::TempDebugGroup debuggroup("Image refresh"); + love::image::ImageDataBase *oldd = data.get(slice, mipmap); - gl.bindTextureToUnit(texture, 0, false); + if (oldd == nullptr) + throw love::Exception("Image does not store ImageData!"); - if (isCompressed()) - { - loadFromCompressedData(); - return true; - } + int w = d->getWidth(); + int h = d->getHeight(); - bool isSRGB = sRGB; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB); + if (w != oldd->getWidth() || h != oldd->getHeight()) + throw love::Exception("Dimensions must match the texture's dimensions for the specified mipmap level."); - int mipcount = settings.mipmaps ? (int) data.size() : 1; + d->retain(); + oldd->release(); - // Reupload the sub-rectangle of each mip level (if we have custom mipmaps.) - for (int i = 0; i < mipcount; i++) - { - const image::pixel *pdata = (const image::pixel *) data[i]->getData(); - pdata += yoffset * data[i]->getWidth() + xoffset; + data.set(slice, mipmap, d); - thread::Lock lock(data[i]->getMutex()); - glTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, w, h, - fmt.externalformat, fmt.type, pdata); + OpenGL::TempDebugGroup debuggroup("Image replace pixels"); - xoffset /= 2; - yoffset /= 2; - w = std::max(w / 2, 1); - h = std::max(h / 2, 1); - } + gl.bindTextureToUnit(this, 0, false); - if (data.size() <= 1) + uploadImageData(d, mipmap, slice); + + if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1) generateMipmaps(); +} - return true; +void Image::replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) +{ + OpenGL::TempDebugGroup debuggroup("Image replace pixels"); + + gl.bindTextureToUnit(this, 0, false); + + uploadByteData(format, data, size, rect, mipmap, slice); + + if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1) + generateMipmaps(); } ptrdiff_t Image::getHandle() const @@ -446,21 +412,11 @@ ptrdiff_t Image::getHandle() const return texture; } -const std::vector> &Image::getImageData() const -{ - return data; -} - -const std::vector> &Image::getCompressedData() const -{ - return cdata; -} - void Image::setFilter(const Texture::Filter &f) { - if (!validateFilter(f, settings.mipmaps)) + if (!validateFilter(f, getMipmapCount() > 1)) { - if (f.mipmap != FILTER_NONE && !settings.mipmaps) + if (f.mipmap != FILTER_NONE && getMipmapCount() == 1) throw love::Exception("Non-mipmapped image cannot have mipmap filtering."); else throw love::Exception("Invalid texture filter."); @@ -468,7 +424,7 @@ void Image::setFilter(const Texture::Filter &f) filter = f; - if (!data.empty() && !OpenGL::hasTextureFilteringSupport(data[0]->getFormat())) + if (!OpenGL::hasTextureFilteringSupport(getPixelFormat())) { filter.mag = filter.min = FILTER_NEAREST; @@ -483,57 +439,64 @@ void Image::setFilter(const Texture::Filter &f) filter.min = filter.mag = FILTER_NEAREST; } - gl.bindTextureToUnit(texture, 0, false); - gl.setTextureFilter(filter); + gl.bindTextureToUnit(this, 0, false); + gl.setTextureFilter(texType, filter); } bool Image::setWrap(const Texture::Wrap &w) { bool success = true; + bool forceclamp = texType == TEXTURE_CUBE; wrap = w; + // If we only have limited NPOT support then the wrap mode must be CLAMP. if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot)) - && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight))) + && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth))) { - if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP) + forceclamp = true; + } + + if (forceclamp) + { + if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP) success = false; - // If we only have limited NPOT support then the wrap mode must be CLAMP. - wrap.s = wrap.t = WRAP_CLAMP; + wrap.s = wrap.t = wrap.r = WRAP_CLAMP; } if (!gl.isClampZeroTextureWrapSupported()) { - if (wrap.s == WRAP_CLAMP_ZERO) - wrap.s = WRAP_CLAMP; - if (wrap.t == WRAP_CLAMP_ZERO) - wrap.t = WRAP_CLAMP; + if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP; + if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP; + if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP; } - gl.bindTextureToUnit(texture, 0, false); - gl.setTextureWrap(wrap); + gl.bindTextureToUnit(this, 0, false); + gl.setTextureWrap(texType, wrap); return success; } -void Image::setMipmapSharpness(float sharpness) +bool Image::setMipmapSharpness(float sharpness) { // OpenGL ES doesn't support LOD bias via glTexParameter. if (!GLAD_VERSION_1_4) - return; + return false; // LOD bias has the range (-maxbias, maxbias) mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f); - gl.bindTextureToUnit(texture, 0, false); + gl.bindTextureToUnit(this, 0, false); // negative bias is sharper - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness); + GLenum gltextype = OpenGL::getGLTextureType(texType); + glTexParameterf(gltextype, GL_TEXTURE_LOD_BIAS, -mipmapSharpness); + return true; } -float Image::getMipmapSharpness() const +bool Image::isFormatLinear() const { - return mipmapSharpness; + return isGammaCorrect() && !sRGB; } bool Image::isCompressed() const @@ -541,6 +504,11 @@ bool Image::isCompressed() const return compressed; } +Image::MipmapsType Image::getMipmapsType() const +{ + return mipmapsType; +} + bool Image::isFormatSupported(PixelFormat pixelformat) { return OpenGL::isPixelFormatSupported(pixelformat, false, false); diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Image.h index 707a6a837..a43c12f91 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Image.h @@ -38,8 +38,8 @@ class Image final : public love::graphics::Image, public Volatile { public: - Image(const std::vector &data, const Settings &settings); - Image(const std::vector &cdata, const Settings &settings); + Image(const Slices &data, const Settings &settings); + Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings); virtual ~Image(); @@ -49,52 +49,37 @@ public: ptrdiff_t getHandle() const override; - const std::vector> &getImageData() const override; - const std::vector> &getCompressedData() const override; - void setFilter(const Texture::Filter &f) override; bool setWrap(const Texture::Wrap &w) override; - void setMipmapSharpness(float sharpness) override; - float getMipmapSharpness() const override; + bool setMipmapSharpness(float sharpness) override; + + void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) override; + void replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) override; + + bool isFormatLinear() const override; bool isCompressed() const override; - bool refresh(int xoffset, int yoffset, int w, int h) override; + MipmapsType getMipmapsType() const override; static bool isFormatSupported(PixelFormat pixelformat); static bool hasSRGBSupport(); - static bool getConstant(const char *in, SettingType &out); - static bool getConstant(SettingType in, const char *&out); - private: - void preload(); + void init(PixelFormat fmt, int w, int h, const Settings &settings); void generateMipmaps(); void loadDefaultTexture(); - void loadFromCompressedData(); - void loadFromImageData(); - - // The ImageData from which the texture is created. May be empty if - // Compressed image data was used to create the texture. - // Each element in the array is a mipmap level. - std::vector> data; - - // Or the Compressed Image Data from which the texture is created. May be - // empty if raw ImageData was used to create the texture. - std::vector> cdata; + void loadData(); + void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, const Rect &rect, int level, int slice); + void uploadImageData(love::image::ImageDataBase *d, int level, int slice); // OpenGL texture identifier. GLuint texture; - // Mipmap texture LOD bias (sharpness) value. - float mipmapSharpness; - // Whether this Image is using a compressed texture. bool compressed; - bool sRGB; - // True if the image wasn't able to be properly created and it had to fall // back to a default texture. bool usingDefaultTexture; diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index f91e20355..76fc2eb75 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/src/modules/graphics/opengl/Mesh.cpp @@ -99,6 +99,9 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m, if (instancecount > 1 && !gl.isInstancingSupported()) throw love::Exception("Instancing is not supported on this system."); + if (Shader::current && texture.get()) + Shader::current->checkMainTextureType(texture->getTextureType()); + gfx->flushStreamDraws(); OpenGL::TempDebugGroup debuggroup("Mesh draw"); @@ -131,7 +134,10 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m, gl.useVertexAttribArrays(enabledattribs, instancedattribs); - gl.bindTextureToUnit(texture, 0, false); + if (texture.get()) + gl.bindTextureToUnit(texture, 0, false); + else + gl.bindTextureToUnit(TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, false); Graphics::TempTransform transform(gfx, m); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 89278c2aa..f74e0663a 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -95,7 +95,10 @@ OpenGL::OpenGL() , contextInitialized(false) , pixelShaderHighpSupported(false) , maxAnisotropy(1.0f) - , maxTextureSize(0) + , max2DTextureSize(0) + , max3DTextureSize(0) + , maxCubeTextureSize(0) + , maxTextureArrayLayers(0) , maxRenderTargets(1) , maxRenderbufferSamples(0) , maxTextureUnits(1) @@ -202,13 +205,18 @@ void OpenGL::setupContext() } // Initialize multiple texture unit support for shaders. - state.boundTextures.clear(); - state.boundTextures.resize(maxTextureUnits, 0); + for (int i = 0; i < TEXTURE_MAX_ENUM; i++) + { + state.boundTextures[i].clear(); + state.boundTextures[i].resize(maxTextureUnits, 0); + } - for (int i = 0; i < (int) state.boundTextures.size(); i++) + for (int i = 0; i < maxTextureUnits; i++) { glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(GL_TEXTURE_2D, 0); + + for (int j = 0; j < TEXTURE_MAX_ENUM; j++) + glBindTexture(getGLTextureType((TextureType) j), 0); } glActiveTexture(GL_TEXTURE0); @@ -224,8 +232,14 @@ void OpenGL::deInitContext() if (!contextInitialized) return; - glDeleteTextures(1, &state.defaultTexture); - state.defaultTexture = 0; + for (int i = 0; i < TEXTURE_MAX_ENUM; i++) + { + if (state.defaultTexture[i] != 0) + { + gl.deleteTexture(state.defaultTexture[i]); + state.defaultTexture[i] = 0; + } + } contextInitialized = false; } @@ -285,11 +299,15 @@ void OpenGL::initOpenGLFunctions() fp_glGenFramebuffers = fp_glGenFramebuffersEXT; fp_glCheckFramebufferStatus = fp_glCheckFramebufferStatusEXT; fp_glFramebufferTexture2D = fp_glFramebufferTexture2DEXT; + fp_glFramebufferTexture3D = fp_glFramebufferTexture3DEXT; fp_glFramebufferRenderbuffer = fp_glFramebufferRenderbufferEXT; fp_glGetFramebufferAttachmentParameteriv = fp_glGetFramebufferAttachmentParameterivEXT; fp_glGenerateMipmap = fp_glGenerateMipmapEXT; } + if (GLAD_VERSION_1_0 && GLAD_EXT_texture_array) + fp_glFramebufferTextureLayer = fp_glFramebufferTextureLayerEXT; + if (GLAD_EXT_framebuffer_blit) fp_glBlitFramebuffer = fp_glBlitFramebufferEXT; else if (GLAD_ANGLE_framebuffer_blit) @@ -328,6 +346,17 @@ void OpenGL::initOpenGLFunctions() fp_glVertexAttribDivisor = fp_glVertexAttribDivisorANGLE; } } + + if (GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0) + { + // Function signatures don't match, we'll have to conditionally call it + //fp_glTexImage3D = fp_glTexImage3DOES; + fp_glTexSubImage3D = fp_glTexSubImage3DOES; + fp_glCopyTexSubImage3D = fp_glCopyTexSubImage3DOES; + fp_glCompressedTexImage3D = fp_glCompressedTexImage3DOES; + fp_glCompressedTexSubImage3D = fp_glCompressedTexSubImage3DOES; + fp_glFramebufferTexture3D = fp_glFramebufferTexture3DOES; + } } void OpenGL::initMaxValues() @@ -348,7 +377,18 @@ void OpenGL::initMaxValues() else maxAnisotropy = 1.0f; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max2DTextureSize); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxCubeTextureSize); + + if (isTextureTypeSupported(TEXTURE_VOLUME)) + glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DTextureSize); + else + max3DTextureSize = 0; + + if (isTextureTypeSupported(TEXTURE_2D_ARRAY)) + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureArrayLayers); + else + maxTextureArrayLayers = 0; int maxattachments = 1; int maxdrawbuffers = 1; @@ -382,26 +422,57 @@ void OpenGL::initMaxValues() void OpenGL::createDefaultTexture() { - // Set the 'default' texture (id 0) as a repeating white pixel. Otherwise, - // texture2D calls inside a shader would return black when drawing graphics - // primitives, which would create the need to use different "passthrough" - // shaders for untextured primitives vs images. + // Set the 'default' texture as a repeating white pixel. Otherwise, texture + // calls inside a shader would return black when drawing graphics primitives + // which would create the need to use different "passthrough" shaders for + // untextured primitives vs images. + const GLubyte pix[] = {255, 255, 255, 255}; - GLuint curtexture = state.boundTextures[state.curTextureUnit]; + Texture::Filter filter; + filter.min = filter.mag = Texture::FILTER_NEAREST; - glGenTextures(1, &state.defaultTexture); - bindTextureToUnit(state.defaultTexture, 0, false); + Texture::Wrap wrap; + wrap.s = wrap.t = wrap.r = Texture::WRAP_CLAMP; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + for (int i = 0; i < TEXTURE_MAX_ENUM; i++) + { + state.defaultTexture[i] = 0; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + TextureType type = (TextureType) i; - GLubyte pix[] = {255, 255, 255, 255}; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix); + if (!isTextureTypeSupported(type)) + continue; - bindTextureToUnit(curtexture, 0, false); + GLuint curtexture = state.boundTextures[type][0]; + + glGenTextures(1, &state.defaultTexture[type]); + bindTextureToUnit(type, state.defaultTexture[type], 0, false); + + setTextureWrap(type, wrap); + setTextureFilter(type, filter); + + bool isSRGB = false; + rawTexStorage(type, 1, PIXELFORMAT_RGBA8, isSRGB, 1, 1); + + TextureFormat fmt = convertPixelFormat(PIXELFORMAT_RGBA8, false, isSRGB); + + int slices = type == TEXTURE_CUBE ? 6 : 1; + + for (int slice = 0; slice < slices; slice++) + { + GLenum gltarget = getGLTextureType(type); + + if (type == TEXTURE_CUBE) + gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice; + + if (type == TEXTURE_2D || type == TEXTURE_CUBE) + glTexSubImage2D(gltarget, 0, 0, 0, 1, 1, fmt.externalformat, fmt.type, pix); + else if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) + glTexSubImage3D(gltarget, 0, 0, 0, slice, 1, 1, 1, fmt.externalformat, fmt.type, pix); + } + + bindTextureToUnit(type, curtexture, 0, false); + } } void OpenGL::prepareDraw() @@ -432,6 +503,27 @@ GLenum OpenGL::getGLBufferType(BufferType type) case BUFFER_MAX_ENUM: return GL_ZERO; } + + return GL_ZERO; +} + +GLenum OpenGL::getGLTextureType(TextureType type) +{ + switch (type) + { + case TEXTURE_2D: + return GL_TEXTURE_2D; + case TEXTURE_VOLUME: + return GL_TEXTURE_3D; + case TEXTURE_2D_ARRAY: + return GL_TEXTURE_2D_ARRAY; + case TEXTURE_CUBE: + return GL_TEXTURE_CUBE_MAP; + case TEXTURE_MAX_ENUM: + return GL_ZERO; + } + + return GL_ZERO; } GLenum OpenGL::getGLIndexDataType(IndexDataType type) @@ -651,6 +743,29 @@ void OpenGL::deleteFramebuffer(GLuint framebuffer) } } +void OpenGL::framebufferTexture(GLenum attachment, TextureType texType, GLuint texture, int level, int layer, int face) +{ + GLenum textarget = getGLTextureType(texType); + + switch (texType) + { + case TEXTURE_2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, texture, level); + break; + case TEXTURE_VOLUME: + glFramebufferTexture3D(GL_FRAMEBUFFER, attachment, textarget, texture, level, layer); + break; + case TEXTURE_2D_ARRAY: + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, texture, level, layer); + break; + case TEXTURE_CUBE: + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, level); + break; + default: + break; + } +} + void OpenGL::useProgram(GLuint program) { glUseProgram(program); @@ -670,9 +785,9 @@ GLuint OpenGL::getDefaultFBO() const #endif } -GLuint OpenGL::getDefaultTexture() const +GLuint OpenGL::getDefaultTexture(TextureType type) const { - return state.defaultTexture; + return state.defaultTexture[type]; } void OpenGL::setTextureUnit(int textureunit) @@ -683,16 +798,16 @@ void OpenGL::setTextureUnit(int textureunit) state.curTextureUnit = textureunit; } -void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev) +void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev) { - if (texture != state.boundTextures[textureunit]) + if (texture != state.boundTextures[target][textureunit]) { int oldtextureunit = state.curTextureUnit; if (oldtextureunit != textureunit) glActiveTexture(GL_TEXTURE0 + textureunit); - state.boundTextures[textureunit] = texture; - glBindTexture(GL_TEXTURE_2D, texture); + state.boundTextures[target][textureunit] = texture; + glBindTexture(getGLTextureType(target), texture); if (restoreprev && oldtextureunit != textureunit) glActiveTexture(GL_TEXTURE0 + oldtextureunit); @@ -703,24 +818,29 @@ void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev) { - GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture(); - bindTextureToUnit(handle, textureunit, restoreprev); + GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture(TEXTURE_2D); + TextureType textype = texture != nullptr ? texture->getTextureType() : TEXTURE_2D; + + bindTextureToUnit(textype, handle, textureunit, restoreprev); } void OpenGL::deleteTexture(GLuint texture) { // glDeleteTextures binds texture 0 to all texture units the deleted texture // was bound to before deletion. - for (GLuint &texid : state.boundTextures) + for (int i = 0; i < TEXTURE_MAX_ENUM; i++) { - if (texid == texture) - texid = 0; + for (GLuint &texid : state.boundTextures[i]) + { + if (texid == texture) + texid = 0; + } } glDeleteTextures(1, &texture); } -void OpenGL::setTextureFilter(graphics::Texture::Filter &f) +void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f) { GLint gmin, gmag; @@ -756,13 +876,15 @@ void OpenGL::setTextureFilter(graphics::Texture::Filter &f) break; } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag); + GLenum gltarget = getGLTextureType(target); + + glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin); + glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, gmag); if (GLAD_EXT_texture_filter_anisotropic) { f.anisotropy = std::min(std::max(f.anisotropy, 1.0f), maxAnisotropy); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy); + glTexParameterf(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy); } else f.anisotropy = 1.0f; @@ -785,10 +907,104 @@ GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode) } -void OpenGL::setTextureWrap(const graphics::Texture::Wrap &w) +void OpenGL::setTextureWrap(TextureType target, const graphics::Texture::Wrap &w) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getGLWrapMode(w.s)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getGLWrapMode(w.t)); + glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_S, getGLWrapMode(w.s)); + glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_T, getGLWrapMode(w.t)); + + if (target == TEXTURE_VOLUME) + glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_R, getGLWrapMode(w.r)); +} + +bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth) +{ + GLenum gltarget = getGLTextureType(target); + TextureFormat fmt = convertPixelFormat(pixelformat, false, isSRGB); + + if (fmt.swizzled) + { + glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]); + glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_G, fmt.swizzle[1]); + glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_B, fmt.swizzle[2]); + glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]); + } + + bool supportsTexStorage = GLAD_VERSION_4_2 || GLAD_ARB_texture_storage; + + // Apparently there are bugs with glTexStorage on some Android drivers. I'd + // rather not find out the hard way, so we'll avoid it for now... +#ifndef LOVE_ANDROID + if (GLAD_ES_VERSION_3_0) + supportsTexStorage = true; +#endif + + if (supportsTexStorage) + { + if (target == TEXTURE_2D || target == TEXTURE_CUBE) + glTexStorage2D(gltarget, levels, fmt.internalformat, width, height); + else if (target == TEXTURE_VOLUME || target == TEXTURE_2D_ARRAY) + glTexStorage3D(gltarget, levels, fmt.internalformat, width, height, depth); + } + else + { + int w = width; + int h = height; + int d = depth; + + for (int level = 0; level < levels; level++) + { + if (target == TEXTURE_2D || target == TEXTURE_CUBE) + { + int faces = target == TEXTURE_CUBE ? 6 : 1; + for (int face = 0; face < faces; face++) + { + if (target == TEXTURE_CUBE) + gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; + + glTexImage2D(gltarget, level, fmt.internalformat, w, h, 0, + fmt.externalformat, fmt.type, nullptr); + } + } + else if (target == TEXTURE_2D_ARRAY || target == TEXTURE_VOLUME) + { + if (target == TEXTURE_VOLUME && GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0) + { + glTexImage3DOES(gltarget, level, fmt.internalformat, w, h, + d, 0, fmt.externalformat, fmt.type, nullptr); + } + else + { + glTexImage3D(gltarget, level, fmt.internalformat, w, h, d, + 0, fmt.externalformat, fmt.type, nullptr); + } + } + + w = std::max(w / 2, 1); + h = std::max(h / 2, 1); + + if (target == TEXTURE_VOLUME) + d = std::max(d / 2, 1); + } + } + + return gltarget != GL_ZERO; +} + +bool OpenGL::isTextureTypeSupported(TextureType type) const +{ + switch (type) + { + case TEXTURE_2D: + return true; + case TEXTURE_VOLUME: + return GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_3D; + case TEXTURE_2D_ARRAY: + return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_array; + case TEXTURE_CUBE: + return GLAD_VERSION_1_3 || GLAD_ES_VERSION_2_0; + default: + return false; + } } bool OpenGL::isClampZeroTextureWrapSupported() const @@ -807,9 +1023,24 @@ bool OpenGL::isInstancingSupported() const || GLAD_ARB_instanced_arrays || GLAD_EXT_instanced_arrays || GLAD_ANGLE_instanced_arrays; } -int OpenGL::getMaxTextureSize() const +int OpenGL::getMax2DTextureSize() const { - return maxTextureSize; + return std::max(max2DTextureSize, 1); +} + +int OpenGL::getMax3DTextureSize() const +{ + return std::max(max3DTextureSize, 1); +} + +int OpenGL::getMaxCubeTextureSize() const +{ + return std::max(maxCubeTextureSize, 1); +} + +int OpenGL::getMaxTextureLayers() const +{ + return std::max(maxTextureArrayLayers, 1); } int OpenGL::getMaxRenderTargets() const diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index b22c366b1..9fb260174 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -248,6 +248,8 @@ public: GLuint getFramebuffer(FramebufferTarget target) const; void deleteFramebuffer(GLuint framebuffer); + void framebufferTexture(GLenum attachment, TextureType texType, GLuint texture, int level, int layer = 0, int face = 0); + /** * Calls glUseProgram. **/ @@ -262,7 +264,7 @@ public: /** * Gets the ID for love's default texture (used for "untextured" primitives.) **/ - GLuint getDefaultTexture() const; + GLuint getDefaultTexture(TextureType type) const; /** * Helper for setting the active texture unit. @@ -277,7 +279,7 @@ public: * @param textureunit Index in the range of [0, maxtextureunits-1] * @param restoreprev Restore previously bound texture unit when done. **/ - void bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev); + void bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev); void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev); /** @@ -291,13 +293,21 @@ public: * The anisotropy parameter of the argument is set to the actual amount of * anisotropy that was used. **/ - void setTextureFilter(graphics::Texture::Filter &f); + void setTextureFilter(TextureType target, graphics::Texture::Filter &f); /** * Sets the texture wrap mode for the currently bound texture. **/ - void setTextureWrap(const graphics::Texture::Wrap &w); + void setTextureWrap(TextureType target, const graphics::Texture::Wrap &w); + /** + * Equivalent to glTexStorage2D/3D on platforms that support it. Equivalent + * to glTexImage2D/3D for all levels and slices of a texture otherwise. + * NOTE: this does not handle compressed texture formats. + **/ + bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1); + + bool isTextureTypeSupported(TextureType type) const; bool isClampZeroTextureWrapSupported() const; bool isPixelShaderHighpSupported() const; bool isInstancingSupported() const; @@ -305,7 +315,10 @@ public: /** * Returns the maximum supported width or height of a texture. **/ - int getMaxTextureSize() const; + int getMax2DTextureSize() const; + int getMax3DTextureSize() const; + int getMaxCubeTextureSize() const; + int getMaxTextureLayers() const; /** * Returns the maximum supported number of simultaneous render targets. @@ -349,6 +362,7 @@ public: static GLenum getGLBufferType(BufferType type); static GLenum getGLIndexDataType(IndexDataType type); static GLenum getGLBufferUsage(vertex::Usage usage); + static GLenum getGLTextureType(TextureType type); static GLint getGLWrapMode(Texture::WrapMode wmode); static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB); @@ -374,7 +388,10 @@ private: bool pixelShaderHighpSupported; float maxAnisotropy; - int maxTextureSize; + int max2DTextureSize; + int max3DTextureSize; + int maxCubeTextureSize; + int maxTextureArrayLayers; int maxRenderTargets; int maxRenderbufferSamples; int maxTextureUnits; @@ -390,7 +407,7 @@ private: GLuint boundBuffers[BUFFER_MAX_ENUM]; // Texture unit state (currently bound texture for each texture unit.) - std::vector boundTextures; + std::vector boundTextures[TEXTURE_MAX_ENUM]; // Currently active texture unit. int curTextureUnit; @@ -410,7 +427,7 @@ private: bool framebufferSRGBEnabled; - GLuint defaultTexture; + GLuint defaultTexture[TEXTURE_MAX_ENUM]; } state; diff --git a/src/modules/graphics/opengl/ParticleSystem.cpp b/src/modules/graphics/opengl/ParticleSystem.cpp index 9fc066c77..85a9b0533 100644 --- a/src/modules/graphics/opengl/ParticleSystem.cpp +++ b/src/modules/graphics/opengl/ParticleSystem.cpp @@ -55,6 +55,9 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m) if (!prepareDraw(gfx, m)) return; + if (Shader::current && texture.get()) + Shader::current->checkMainTextureType(texture->getTextureType()); + OpenGL::TempDebugGroup debuggroup("ParticleSystem draw"); gl.bindTextureToUnit(texture, 0, false); diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 967315d04..6200b1169 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -40,11 +40,11 @@ Shader::Shader(const ShaderSource &source) : love::graphics::Shader(source) , program(0) , builtinUniforms() + , builtinUniformInfo() , builtinAttributes() , canvasWasActive(false) , lastViewport() , lastPointSize(0.0f) - , videoTextureUnits() { // load shader source and create program object loadVolatile(); @@ -142,7 +142,10 @@ void Shader::mapActiveUniforms() { // Built-in uniform locations default to -1 (nonexistent.) for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++) + { builtinUniforms[i] = -1; + builtinUniformInfo[i] = nullptr; + } GLint activeprogram = 0; glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram); @@ -158,17 +161,18 @@ void Shader::mapActiveUniforms() std::map olduniforms = uniforms; uniforms.clear(); - for (int i = 0; i < numuniforms; i++) + for (int uindex = 0; uindex < numuniforms; uindex++) { GLsizei namelen = 0; GLenum gltype = 0; UniformInfo u = {}; - glGetActiveUniform(program, (GLuint) i, bufsize, &namelen, &u.count, &gltype, cname); + glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &u.count, &gltype, cname); u.name = std::string(cname, (size_t) namelen); u.location = glGetUniformLocation(program, u.name.c_str()); u.baseType = getUniformBaseType(gltype); + u.textureType = getUniformTextureType(gltype); if (u.baseType == UNIFORM_MATRIX) u.matrix = getMatrixSize(gltype); @@ -184,13 +188,24 @@ void Shader::mapActiveUniforms() } // If this is a built-in (LOVE-created) uniform, store the location. - BuiltinUniform builtin; + BuiltinUniform builtin = BUILTIN_MAX_ENUM; if (getConstant(u.name.c_str(), builtin)) builtinUniforms[int(builtin)] = u.location; if (u.location == -1) continue; + if (u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) + { + TextureUnit unit; + unit.type = u.textureType; + unit.active = true; + unit.texture = gl.getDefaultTexture(u.textureType); + + for (int i = 0; i < u.count; i++) + textureUnits.push_back(unit); + } + // Make sure previously set uniform data is preserved, and shader- // initialized values are retrieved. auto oldu = olduniforms.find(u.name); @@ -200,23 +215,6 @@ void Shader::mapActiveUniforms() u.textures = oldu->second.textures; updateUniform(&u, u.count, true); - - if (u.baseType == UNIFORM_SAMPLER) - { - // Make sure all stored textures have their Volatiles loaded - // before the sendTextures call, since it calls getHandle(). - for (int i = 0; i < u.count; i++) - { - if (u.textures[i] == nullptr) - continue; - - Volatile *v = dynamic_cast(u.textures[i]); - if (v != nullptr) - v->loadVolatile(); - } - - sendTextures(&u, u.textures, u.count, true); - } } else { @@ -252,9 +250,14 @@ void Shader::mapActiveUniforms() if (u.baseType == UNIFORM_SAMPLER) { - // Initialize all samplers to 0. Both GLSL and GLSL ES are - // supposed to do this themselves, but some Android devices - // (galaxy tab 3 and 4) don't seem to do it... + int startunit = (int) textureUnits.size() - u.count; + + if (builtin == BUILTIN_TEXTURE_MAIN) + startunit = 0; + + for (int i = 0; i < u.count; i++) + u.ints[i] = startunit + i; + glUniform1iv(u.location, u.count, u.ints); u.textures = new Texture*[u.count]; @@ -307,6 +310,26 @@ void Shader::mapActiveUniforms() } uniforms[u.name] = u; + + if (builtin != BUILTIN_MAX_ENUM) + builtinUniformInfo[(int)builtin] = &uniforms[u.name]; + + if (u.baseType == UNIFORM_SAMPLER) + { + // Make sure all stored textures have their Volatiles loaded before + // the sendTextures call, since it calls getHandle(). + for (int i = 0; i < u.count; i++) + { + if (u.textures[i] == nullptr) + continue; + + Volatile *v = dynamic_cast(u.textures[i]); + if (v != nullptr) + v->loadVolatile(); + } + + sendTextures(&u, u.textures, u.count, true); + } } // Make sure uniforms that existed before but don't exist anymore are @@ -348,12 +371,9 @@ bool Shader::loadVolatile() lastProjectionMatrix.setTranslation(nan, nan); lastTransformMatrix.setTranslation(nan, nan); - for (int i = 0; i < 3; i++) - videoTextureUnits[i] = 0; - // zero out active texture list textureUnits.clear(); - textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit()); + textureUnits.push_back(TextureUnit()); std::vector shaderids; @@ -449,7 +469,7 @@ void Shader::unloadVolatile() // active texture list is probably invalid, clear it textureUnits.clear(); - textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit()); + textureUnits.push_back(TextureUnit()); attributes.clear(); @@ -497,7 +517,7 @@ std::string Shader::getWarnings() const return warnings; } -void Shader::attach(bool temporary) +void Shader::attach() { if (current != this) { @@ -505,22 +525,19 @@ void Shader::attach(bool temporary) current = this; // retain/release happens in Graphics::setShader. - if (!temporary) + // Make sure all textures are bound to their respective texture units. + for (int i = 0; i < (int) textureUnits.size(); ++i) { - // Make sure all textures are properly bound to their respective - // texture units. - for (int i = 1; i < (int) textureUnits.size(); ++i) - { - if (textureUnits[i].active) - gl.bindTextureToUnit(textureUnits[i].texture, i, false); - } - - // send any pending uniforms to the shader program. - for (const auto &p : pendingUniformUpdates) - updateUniform(p.first, p.second); - - pendingUniformUpdates.clear(); + const TextureUnit &unit = textureUnits[i]; + if (unit.active) + gl.bindTextureToUnit(unit.type, unit.texture, i, false); } + + // send any pending uniforms to the shader program. + for (const auto &p : pendingUniformUpdates) + updateUniform(p.first, p.second, true); + + pendingUniformUpdates.clear(); } } @@ -534,15 +551,25 @@ const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const return &(it->second); } -void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpdate) +const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const { - if (current != this) + return builtinUniformInfo[(int)builtin]; +} + +void Shader::updateUniform(const UniformInfo *info, int count) +{ + updateUniform(info, count, false); +} + +void Shader::updateUniform(const UniformInfo *info, int count, bool internalupdate) +{ + if (current != this && !internalupdate) { pendingUniformUpdates.push_back(std::make_pair(info, count)); return; } - if (!internalUpdate) + if (!internalupdate) flushStreamDraws(); int location = info->location; @@ -628,24 +655,9 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpda } } -int Shader::getFreeTextureUnits(int count) +void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count) { - int startunit = -1; - - // Ignore the first texture unit for Shader-local texture bindings. - for (int i = 1; i < (int) textureUnits.size(); i++) - { - if (!textureUnits[i].active && i + count <= (int) textureUnits.size()) - { - startunit = i; - break; - } - } - - if (startunit == -1) - throw love::Exception("No more texture units available for shader."); - - return startunit; + Shader::sendTextures(info, textures, count, false); } void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate) @@ -659,55 +671,36 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count flushStreamDraws(); count = std::min(count, info->count); - bool updateuniform = false; - - // Make sure the shader's samplers are associated with texture units. - for (int i = 0; i < count; i++) - { - if (info->ints[i] == 0 && textures[i] != nullptr) - { - int texunit = getFreeTextureUnits(1); - textureUnits[texunit].active = true; - - info->ints[i] = texunit; - updateuniform = true; - } - } - - if (updateuniform) - updateUniform(info, count, internalUpdate); // Bind the textures to the texture units. for (int i = 0; i < count; i++) { if (textures[i] != nullptr) + { + if (textures[i]->getTextureType() != info->textureType) + continue; + textures[i]->retain(); + } if (info->textures[i] != nullptr) info->textures[i]->release(); info->textures[i] = textures[i]; + GLuint gltex = 0; + if (textures[i] != nullptr) + gltex = (GLuint) textures[i]->getHandle(); + else + gltex = gl.getDefaultTexture(info->textureType); + int texunit = info->ints[i]; - if (textures[i] != nullptr) - { - GLuint gltex = (GLuint) textures[i]->getHandle(); + if (shaderactive) + gl.bindTextureToUnit(info->textureType, gltex, texunit, false); - if (shaderactive) - gl.bindTextureToUnit(gltex, texunit, false); - - // Store texture id so it can be re-bound to the texture unit later. - textureUnits[texunit].texture = gltex; - } - else - { - if (shaderactive) - gl.bindTextureToUnit((GLuint) 0, texunit, false); - - textureUnits[texunit].texture = 0; - textureUnits[texunit].active = false; - } + // Store texture id so it can be re-bound to the texture unit later. + textureUnits[texunit].texture = gltex; } } @@ -743,50 +736,22 @@ GLint Shader::getAttribLocation(const std::string &name) return location; } -void Shader::setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) +void Shader::setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) { - // Set up the texture units that will be used by the shader to sample from - // the textures, if they haven't been set up yet. - if (videoTextureUnits[0] == 0) - { - const BuiltinUniform builtins[3] = { - BUILTIN_TEXTURE_VIDEO_Y, - BUILTIN_TEXTURE_VIDEO_CB, - BUILTIN_TEXTURE_VIDEO_CR, - }; + const BuiltinUniform builtins[3] = { + BUILTIN_TEXTURE_VIDEO_Y, + BUILTIN_TEXTURE_VIDEO_CB, + BUILTIN_TEXTURE_VIDEO_CR, + }; - for (int i = 0; i < 3; i++) - { - GLint loc = builtinUniforms[builtins[i]]; - const char *name = nullptr;; + Texture *textures[3] = {ytexture, cbtexture, crtexture}; - if (loc >= 0 && getConstant(builtins[i], name) && name != nullptr) - { - const UniformInfo *info = getUniformInfo(name); - if (info == nullptr) - continue; - - videoTextureUnits[i] = getFreeTextureUnits(1); - textureUnits[videoTextureUnits[i]].active = true; - - info->ints[0] = videoTextureUnits[i]; - updateUniform(info, 1); - } - } - } - - const GLuint textures[3] = {(GLuint) ytexture, (GLuint) cbtexture, (GLuint) crtexture}; - - // Bind the textures to their respective texture units. for (int i = 0; i < 3; i++) { - if (videoTextureUnits[i] != 0) - { - // Store texture id so it can be re-bound later. - textureUnits[videoTextureUnits[i]].texture = textures[i]; - if (current == this) - gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false); - } + const UniformInfo *info = builtinUniformInfo[builtins[i]]; + + if (info != nullptr) + sendTextures(info, &textures[i], 1); } } @@ -925,15 +890,15 @@ bool Shader::isSupported() int Shader::getUniformTypeComponents(GLenum type) const { + if (getUniformBaseType(type) == UNIFORM_SAMPLER) + return 1; + switch (type) { case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: case GL_BOOL: - case GL_SAMPLER_1D: - case GL_SAMPLER_2D: - case GL_SAMPLER_3D: return 1; case GL_INT_VEC2: case GL_UNSIGNED_INT_VEC2: @@ -1059,6 +1024,44 @@ Shader::UniformType Shader::getUniformBaseType(GLenum type) const } } +TextureType Shader::getUniformTextureType(GLenum type) const +{ + switch (type) + { + case GL_SAMPLER_1D: + case GL_SAMPLER_1D_SHADOW: + case GL_SAMPLER_1D_ARRAY: + case GL_SAMPLER_1D_ARRAY_SHADOW: + // 1D-typed textures are not supported. + return TEXTURE_MAX_ENUM; + case GL_SAMPLER_2D: + //case GL_SAMPLER_2D_SHADOW: + return TEXTURE_2D; + case GL_SAMPLER_2D_MULTISAMPLE: + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: + // Multisample textures are not supported. + return TEXTURE_MAX_ENUM; + case GL_SAMPLER_2D_RECT: + case GL_SAMPLER_2D_RECT_SHADOW: + // Rectangle textures are not supported. + return TEXTURE_MAX_ENUM; + case GL_SAMPLER_2D_ARRAY: + //case GL_SAMPLER_2D_ARRAY_SHADOW: + return TEXTURE_2D_ARRAY; + case GL_SAMPLER_3D: + return TEXTURE_VOLUME; + case GL_SAMPLER_CUBE: + //case GL_SAMPLER_CUBE_SHADOW: + return TEXTURE_CUBE; + case GL_SAMPLER_CUBE_MAP_ARRAY: + case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW: + // Cubemap array textures are not supported. + return TEXTURE_MAX_ENUM; + default: + return TEXTURE_MAX_ENUM; + } +} + } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 826eb57df..ac554ba73 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -56,14 +56,15 @@ public: void unloadVolatile() override; // Implements Shader. - void attach(bool temporary = false) override; + void attach() override; std::string getWarnings() const override; const UniformInfo *getUniformInfo(const std::string &name) const override; - void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false) override; - void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false) override; + const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override; + void updateUniform(const UniformInfo *info, int count) override; + void sendTextures(const UniformInfo *info, Texture **textures, int count) override; bool hasUniform(const std::string &name) const override; ptrdiff_t getHandle() const override; - void setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) override; + void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) override; GLint getAttribLocation(const std::string &name); @@ -79,20 +80,23 @@ private: struct TextureUnit { GLuint texture = 0; + TextureType type = TEXTURE_2D; bool active = false; }; // Map active uniform names to their locations. void mapActiveUniforms(); + void updateUniform(const UniformInfo *info, int count, bool internalupdate); + void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalupdate); + int getUniformTypeComponents(GLenum type) const; MatrixSize getMatrixSize(GLenum type) const; UniformType getUniformBaseType(GLenum type) const; + TextureType getUniformTextureType(GLenum type) const; GLuint compileCode(ShaderStage stage, const std::string &code); - int getFreeTextureUnits(int count); - void flushStreamDraws() const; // Get any warnings or errors generated only by the shader program object. @@ -106,6 +110,7 @@ private: // Location values for any built-in uniform variables. GLint builtinUniforms[BUILTIN_MAX_ENUM]; + UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM]; // Location values for any generic vertex attribute variables. GLint builtinAttributes[ATTRIB_MAX_ENUM]; @@ -128,8 +133,6 @@ private: Matrix4 lastTransformMatrix; Matrix4 lastProjectionMatrix; - GLuint videoTextureUnits[3]; - }; // Shader } // opengl diff --git a/src/modules/graphics/opengl/SpriteBatch.cpp b/src/modules/graphics/opengl/SpriteBatch.cpp index ef0cc203f..c97bae8c3 100644 --- a/src/modules/graphics/opengl/SpriteBatch.cpp +++ b/src/modules/graphics/opengl/SpriteBatch.cpp @@ -53,38 +53,37 @@ SpriteBatch::~SpriteBatch() void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) { - const size_t pos_offset = offsetof(Vertex, x); - const size_t texel_offset = offsetof(Vertex, s); - const size_t color_offset = offsetof(Vertex, color.r); - if (next == 0) return; gfx->flushStreamDraws(); + if (Shader::current && texture.get()) + Shader::current->checkMainTextureType(texture->getTextureType()); + OpenGL::TempDebugGroup debuggroup("SpriteBatch draw"); Graphics::TempTransform transform(gfx, m); gl.bindTextureToUnit(texture, 0, false); - uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD; - // Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.) array_buf->unmap(); gl.bindBuffer(BUFFER_VERTEX, (GLuint) array_buf->getHandle()); + uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD; + + glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, x))); + glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, s))); + // Apply per-sprite color, if a color is set. if (color) { enabledattribs |= ATTRIBFLAG_COLOR; - glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), BUFFER_OFFSET(color_offset)); + glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, color.r))); } - glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(pos_offset)); - glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(texel_offset)); - for (const auto &it : attached_attributes) { Mesh *mesh = it.second.mesh.get(); diff --git a/src/modules/graphics/opengl/Text.cpp b/src/modules/graphics/opengl/Text.cpp index db6108fd5..8c5849b86 100644 --- a/src/modules/graphics/opengl/Text.cpp +++ b/src/modules/graphics/opengl/Text.cpp @@ -45,6 +45,9 @@ void Text::draw(Graphics *gfx, const Matrix4 &m) if (vbo == nullptr || draw_commands.empty()) return; + if (Shader::current) + Shader::current->checkMainTextureType(TEXTURE_2D); + gfx->flushStreamDraws(); OpenGL::TempDebugGroup debuggroup("Text object draw"); @@ -60,28 +63,24 @@ void Text::draw(Graphics *gfx, const Matrix4 &m) if ((size_t) totalverts / 4 > quadIndices.getSize()) quadIndices = QuadIndices(gfx, (size_t) totalverts / 4); - const size_t pos_offset = offsetof(Font::GlyphVertex, x); - const size_t tex_offset = offsetof(Font::GlyphVertex, s); - const size_t color_offset = offsetof(Font::GlyphVertex, color.r); - const size_t stride = sizeof(Font::GlyphVertex); - - const GLenum gltype = OpenGL::getGLIndexDataType(quadIndices.getType()); - const size_t elemsize = quadIndices.getElementSize(); + vbo->unmap(); // Make sure all pending data is flushed to the GPU. Graphics::TempTransform transform(gfx, m); gl.prepareDraw(); - vbo->unmap(); // Make sure all pending data is flushed to the GPU. + size_t stride = sizeof(Font::GlyphVertex); gl.bindBuffer(BUFFER_VERTEX, (GLuint) vbo->getHandle()); - - glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(pos_offset)); - glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(tex_offset)); - glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(color_offset)); + glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, x))); + glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, s))); + glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, color.r))); gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR); + const GLenum gltype = OpenGL::getGLIndexDataType(quadIndices.getType()); + const size_t elemsize = quadIndices.getElementSize(); + gl.bindBuffer(BUFFER_INDEX, (GLuint) quadIndices.getBuffer()->getHandle()); // We need a separate draw call for every section of the text which uses a @@ -92,7 +91,7 @@ void Text::draw(Graphics *gfx, const Matrix4 &m) size_t offset = (cmd.startvertex / 4) * 6 * elemsize; // TODO: Use glDrawElementsBaseVertex when supported? - gl.bindTextureToUnit((GLuint) cmd.texture, 0, false); + gl.bindTextureToUnit(cmd.texture, 0, false); gl.drawElements(GL_TRIANGLES, count, gltype, BUFFER_OFFSET(offset)); } diff --git a/src/modules/graphics/opengl/Video.cpp b/src/modules/graphics/opengl/Video.cpp deleted file mode 100644 index 59cabda5a..000000000 --- a/src/modules/graphics/opengl/Video.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) 2006-2017 LOVE Development Team - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - **/ - -#include "Video.h" - -namespace love -{ -namespace graphics -{ -namespace opengl -{ - -Video::Video(love::video::VideoStream *stream, float pixeldensity) - : love::graphics::Video(stream, pixeldensity) -{ - loadVolatile(); -} - -Video::~Video() -{ - unloadVolatile(); -} - -bool Video::loadVolatile() -{ - GLuint textures[3]; - glGenTextures(3, textures); - - for (int i = 0; i < 3; i++) - textureHandles[i] = textures[i]; - - // Create the textures using the initial frame data. - auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer(); - - int widths[3] = {frame->yw, frame->cw, frame->cw}; - int heights[3] = {frame->yh, frame->ch, frame->ch}; - - const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane}; - - Texture::Wrap wrap; // Clamp wrap mode. - - bool srgb = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(PIXELFORMAT_R8, false, srgb); - - for (int i = 0; i < 3; i++) - { - gl.bindTextureToUnit(textures[i], 0, false); - - gl.setTextureFilter(filter); - gl.setTextureWrap(wrap); - - glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, widths[i], heights[i], - 0, fmt.externalformat, fmt.type, data[i]); - } - - return true; -} - -void Video::unloadVolatile() -{ - for (int i = 0; i < 3; i++) - { - gl.deleteTexture((GLuint) textureHandles[i]); - textureHandles[i] = 0; - } -} - -void Video::uploadFrame(const love::video::VideoStream::Frame *frame) -{ - int widths[3] = {frame->yw, frame->cw, frame->cw}; - int heights[3] = {frame->yh, frame->ch, frame->ch}; - - const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane}; - - bool srgb = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(PIXELFORMAT_R8, false, srgb); - - for (int i = 0; i < 3; i++) - { - gl.bindTextureToUnit((GLuint) textureHandles[i], 0, false); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widths[i], heights[i], - fmt.externalformat, fmt.type, data[i]); - } -} - -void Video::setFilter(const Texture::Filter &f) -{ - if (!Texture::validateFilter(f, false)) - throw love::Exception("Invalid texture filter."); - - filter = f; - - for (int i = 0; i < 3; i++) - { - gl.bindTextureToUnit((GLuint) textureHandles[i], 0, false); - gl.setTextureFilter(filter); - } -} - -} // opengl -} // graphics -} // love diff --git a/src/modules/graphics/wrap_Canvas.cpp b/src/modules/graphics/wrap_Canvas.cpp index e305782c7..6ea98bbe7 100644 --- a/src/modules/graphics/wrap_Canvas.cpp +++ b/src/modules/graphics/wrap_Canvas.cpp @@ -31,18 +31,6 @@ Canvas *luax_checkcanvas(lua_State *L, int idx) return luax_checktype(L, idx); } -int w_Canvas_getFormat(lua_State *L) -{ - Canvas *canvas = luax_checkcanvas(L, 1); - PixelFormat format = canvas->getPixelFormat(); - const char *str; - if (!getConstant(format, str)) - return luaL_error(L, "Unknown pixel format."); - - lua_pushstring(L, str); - return 1; -} - int w_Canvas_getMSAA(lua_State *L) { Canvas *canvas = luax_checkcanvas(L, 1); @@ -52,28 +40,37 @@ int w_Canvas_getMSAA(lua_State *L) int w_Canvas_renderTo(lua_State *L) { - Canvas *canvas = luax_checkcanvas(L, 1); - luaL_checktype(L, 2, LUA_TFUNCTION); + Graphics::RenderTarget rt(luax_checkcanvas(L, 1)); + + int startidx = 2; + + if (rt.canvas->getTextureType() != TEXTURE_2D) + { + rt.slice = (int) luaL_checknumber(L, 2) - 1; + startidx++; + } + + luaL_checktype(L, startidx, LUA_TFUNCTION); auto graphics = Module::getInstance(Module::M_GRAPHICS); if (graphics) { - // Save the current Canvas so we can restore it when we're done. - std::vector oldcanvases = graphics->getCanvas(); + // Save the current render targets so we can restore them when we're done. + std::vector oldtargets = graphics->getCanvas(); - for (Canvas *c : oldcanvases) - c->retain(); + for (auto c : oldtargets) + c.canvas->retain(); - luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); }); + luax_catchexcept(L, [&](){ graphics->setCanvas(rt); }); lua_settop(L, 2); // make sure the function is on top of the stack int status = lua_pcall(L, 0, 0, 0); - graphics->setCanvas(oldcanvases); + graphics->setCanvas(oldtargets); - for (Canvas *c : oldcanvases) - c->release(); + for (auto c : oldtargets) + c.canvas->release(); if (status != 0) return lua_error(L); @@ -86,13 +83,31 @@ int w_Canvas_newImageData(lua_State *L) { Canvas *canvas = luax_checkcanvas(L, 1); love::image::Image *image = luax_getmodule(L, love::image::Image::type); - int x = (int) luaL_optnumber(L, 2, 0); - int y = (int) luaL_optnumber(L, 3, 0); - int w = (int) luaL_optnumber(L, 4, canvas->getPixelWidth()); - int h = (int) luaL_optnumber(L, 5, canvas->getPixelHeight()); + + int slice = 0; + int x = 0; + int y = 0; + int w = canvas->getPixelWidth(); + int h = canvas->getPixelHeight(); + + int startidx = 2; + + if (canvas->getTextureType() != TEXTURE_2D) + { + slice = (int) luaL_checknumber(L, startidx); + startidx++; + } + + if (!lua_isnoneornil(L, startidx)) + { + x = (int) luaL_checknumber(L, startidx + 0); + y = (int) luaL_checknumber(L, startidx + 1); + w = (int) luaL_checknumber(L, startidx + 2); + h = (int) luaL_checknumber(L, startidx + 3); + } love::image::ImageData *img = nullptr; - luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); }); + luax_catchexcept(L, [&](){ img = canvas->newImageData(image, slice, x, y, w, h); }); luax_pushtype(L, img); img->release(); @@ -101,7 +116,6 @@ int w_Canvas_newImageData(lua_State *L) static const luaL_Reg w_Canvas_functions[] = { - { "getFormat", w_Canvas_getFormat }, { "getMSAA", w_Canvas_getMSAA }, { "renderTo", w_Canvas_renderTo }, { "newImageData", w_Canvas_newImageData }, diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index ce0c93bfd..fd2bc4a01 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -216,26 +216,67 @@ int w_setCanvas(lua_State *L) } bool is_table = lua_istable(L, 1); - std::vector canvases; + std::vector targets; if (is_table) { + lua_rawgeti(L, 1, 1); + bool table_of_tables = lua_istable(L, -1); + lua_pop(L, 1); + for (int i = 1; i <= (int) luax_objlen(L, 1); i++) { lua_rawgeti(L, 1, i); - canvases.push_back(luax_checkcanvas(L, -1)); + + if (table_of_tables) + { + lua_rawgeti(L, -1, 1); + Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0); + lua_pop(L, 1); + + TextureType type = target.canvas->getTextureType(); + if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) + target.slice = luax_checkintflag(L, -1, "layer") - 1; + else if (type == TEXTURE_CUBE) + target.slice = luax_checkintflag(L, -1, "face") - 1; + + targets.push_back(target); + } + else + { + targets.emplace_back(luax_checkcanvas(L, -1), 0); + + if (targets.back().canvas->getTextureType() != TEXTURE_2D) + return luaL_error(L, "The table-of-tables variant of setCanvas must be used with non-2D Canvases."); + } + lua_pop(L, 1); } } else { for (int i = 1; i <= lua_gettop(L); i++) - canvases.push_back(luax_checkcanvas(L, i)); + { + Graphics::RenderTarget target(luax_checkcanvas(L, i), 0); + TextureType type = target.canvas->getTextureType(); + + if (i == 1 && type != TEXTURE_2D) + { + target.slice = (int) luaL_checknumber(L, 2) - 1; + targets.push_back(target); + break; + } + + if (i > 1 && type != TEXTURE_2D) + return luaL_error(L, "This variant of setCanvas only supports 2D texture types."); + + targets.push_back(target); + } } luax_catchexcept(L, [&]() { - if (canvases.size() > 0) - instance()->setCanvas(canvases); + if (targets.size() > 0) + instance()->setCanvas(targets); else instance()->setCanvas(); }); @@ -245,22 +286,63 @@ int w_setCanvas(lua_State *L) int w_getCanvas(lua_State *L) { - const std::vector canvases = instance()->getCanvas(); - int n = 0; + const std::vector targets = instance()->getCanvas(); + int ntargets = (int) targets.size(); - for (Canvas *c : canvases) - { - luax_pushtype(L, c); - n++; - } - - if (n == 0) + if (ntargets == 0) { lua_pushnil(L); - n = 1; + return 1; + } + + bool hasNon2DTextureType = false; + for (const auto &rt : targets) + { + if (rt.canvas->getTextureType() != TEXTURE_2D) + { + hasNon2DTextureType = true; + break; + } + } + + if (hasNon2DTextureType) + { + lua_createtable(L, ntargets, 0); + + for (int i = 0; i < ntargets; i++) + { + const auto &rt = targets[i]; + + lua_createtable(L, 1, 1); + + luax_pushtype(L, rt.canvas); + lua_rawseti(L, -2, 1); + + TextureType type = rt.canvas->getTextureType(); + + if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) + { + lua_pushnumber(L, rt.slice + 1); + lua_setfield(L, -2, "layer"); + } + else if (type == TEXTURE_VOLUME) + { + lua_pushnumber(L, rt.slice + 1); + lua_setfield(L, -2, "face"); + } + + lua_rawseti(L, -2, i + 1); + } + + return 1; + } + else + { + for (const auto &rt : targets) + luax_pushtype(L, rt.canvas); + + return ntargets; } - - return n; } static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd) @@ -413,130 +495,325 @@ int w_getStencilTest(lua_State *L) return 2; } +static void parsePixelDensity(love::filesystem::FileData *d, float *pixeldensity) +{ + // Parse a density scale of 2.0 from "image@2x.png". + const std::string &fname = d->getName(); + + size_t namelen = fname.length(); + size_t atpos = fname.rfind('@'); + + if (atpos != std::string::npos && atpos + 2 < namelen + && (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X')) + { + char *end = nullptr; + long density = strtol(fname.c_str() + atpos + 1, &end, 10); + if (end != nullptr && density > 0 && pixeldensity != nullptr) + *pixeldensity = (float) density; + } +} + +static Image::Settings w__optImageSettings(lua_State *L, int idx, const Image::Settings &s) +{ + Image::Settings settings = s; + + if (!lua_isnoneornil(L, idx)) + { + luaL_checktype(L, idx, LUA_TTABLE); + settings.mipmaps = luax_boolflag(L, idx, "mipmaps", s.mipmaps); + settings.linear = luax_boolflag(L, idx, "linear", s.linear); + settings.pixeldensity = (float) luax_numberflag(L, idx, "pixeldensity", s.pixeldensity); + } + + return settings; +} + +static std::pair, StrongRef> +getImageData(lua_State *L, int idx, bool allowcompressed, float *density) +{ + StrongRef idata; + StrongRef cdata; + + // Convert to ImageData / CompressedImageData, if necessary. + if (lua_isstring(L, idx) || luax_istype(L, idx, love::filesystem::File::type) || luax_istype(L, idx, love::filesystem::FileData::type)) + { + auto imagemodule = Module::getInstance(Module::M_IMAGE); + if (imagemodule == nullptr) + luaL_error(L, "Cannot load images without the love.image module."); + + StrongRef fdata(love::filesystem::luax_getfiledata(L, idx), Acquire::NORETAIN); + + if (density != nullptr) + parsePixelDensity(fdata, density); + + if (allowcompressed && imagemodule->isCompressed(fdata)) + luax_catchexcept(L, [&]() { cdata.set(imagemodule->newCompressedData(fdata), Acquire::NORETAIN); }); + else + luax_catchexcept(L, [&]() { idata.set(imagemodule->newImageData(fdata), Acquire::NORETAIN); }); + + } + else if (luax_istype(L, idx, love::image::CompressedImageData::type)) + cdata.set(love::image::luax_checkcompressedimagedata(L, idx)); + else + idata.set(love::image::luax_checkimagedata(L, idx)); + + return std::make_pair(idata, cdata); +} + +static int w__pushNewImage(lua_State *L, Image::Slices &slices, const Image::Settings &settings) +{ + StrongRef i; + luax_catchexcept(L, + [&]() { i.set(instance()->newImage(slices, settings), Acquire::NORETAIN); }, + [&](bool) { slices.clear(); } + ); + + luax_pushtype(L, i); + return 1; +} + +int w_newCubeImage(lua_State *L) +{ + luax_checkgraphicscreated(L); + + Image::Slices slices(TEXTURE_CUBE); + Image::Settings settings; + + auto imagemodule = Module::getInstance(Module::M_IMAGE); + + if (!lua_istable(L, 1)) + { + auto imagedata = getImageData(L, 1, false, &settings.pixeldensity); + + std::vector> faces; + luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(imagedata.first); }); + + for (int i = 0; i < (int) faces.size(); i++) + slices.set(i, 0, faces[i]); + } + else + { + int tlen = (int) luax_objlen(L, 1); + + if (luax_isarrayoftables(L, 1)) + { + if (tlen != 6) + return luaL_error(L, "Cubemap images must have 6 faces."); + + for (int face = 0; face < tlen; face++) + { + lua_rawgeti(L, 1, face + 1); + luaL_checktype(L, -1, LUA_TTABLE); + + int miplen = std::max(1, (int) luax_objlen(L, -1)); + + for (int mip = 0; mip < miplen; mip++) + { + lua_rawgeti(L, -1, mip + 1); + + auto data = getImageData(L, -1, true, face == 0 && mip == 0 ? &settings.pixeldensity : nullptr); + if (data.first.get()) + slices.set(face, mip, data.first); + else + slices.set(face, mip, data.second->getSlice(0, 0)); + + lua_pop(L, 1); + } + } + } + else + { + bool usemipmaps = false; + + for (int i = 0; i < tlen; i++) + { + lua_rawgeti(L, 1, i + 1); + + auto data = getImageData(L, -1, true, i == 0 ? &settings.pixeldensity : nullptr); + + if (data.first.get()) + { + if (usemipmaps || data.first->getWidth() != data.first->getHeight()) + { + usemipmaps = true; + + std::vector> faces; + luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(data.first); }); + + for (int face = 0; face < (int) faces.size(); face++) + slices.set(face, i, faces[i]); + } + else + slices.set(i, 0, data.first); + } + else + slices.add(data.second, i, 0, false, true); + } + } + + lua_pop(L, tlen); + } + + settings = w__optImageSettings(L, 2, settings); + return w__pushNewImage(L, slices, settings); +} + +int w_newArrayImage(lua_State *L) +{ + luax_checkgraphicscreated(L); + + Image::Slices slices(TEXTURE_2D_ARRAY); + Image::Settings settings; + + if (lua_istable(L, 1)) + { + int tlen = std::max(1, (int) luax_objlen(L, 1)); + + if (luax_isarrayoftables(L, 1)) + { + for (int slice = 0; slice < tlen; slice++) + { + lua_rawgeti(L, 1, slice + 1); + luaL_checktype(L, -1, LUA_TTABLE); + + int miplen = std::max(1, (int) luax_objlen(L, -1)); + + for (int mip = 0; mip < miplen; mip++) + { + lua_rawgeti(L, -1, mip + 1); + + auto data = getImageData(L, -1, true, slice == 0 && mip == 0 ? &settings.pixeldensity : nullptr); + if (data.first.get()) + slices.set(slice, mip, data.first); + else + slices.set(slice, mip, data.second->getSlice(0, 0)); + + lua_pop(L, 1); + } + } + } + else + { + for (int slice = 0; slice < tlen; slice++) + { + lua_rawgeti(L, 1, slice + 1); + auto data = getImageData(L, -1, true, slice == 0 ? &settings.pixeldensity : nullptr); + if (data.first.get()) + slices.set(slice, 0, data.first); + else + slices.add(data.second, slice, 0, false, true); + } + } + + lua_pop(L, tlen); + } + else + { + auto data = getImageData(L, 1, true, &settings.pixeldensity); + if (data.first.get()) + slices.set(0, 0, data.first); + else + slices.add(data.second, 0, 0, true, true); + } + + settings = w__optImageSettings(L, 2, settings); + return w__pushNewImage(L, slices, settings); +} + +int w_newVolumeImage(lua_State *L) +{ + luax_checkgraphicscreated(L); + + Image::Slices slices(TEXTURE_VOLUME); + Image::Settings settings; + + if (lua_istable(L, 1)) + { + int tlen = std::max(1, (int) luax_objlen(L, 1)); + + if (luax_isarrayoftables(L, 1)) + { + for (int mip = 0; mip < tlen; mip++) + { + lua_rawgeti(L, 1, mip + 1); + luaL_checktype(L, -1, LUA_TTABLE); + + int slicelen = std::max(1, (int) luax_objlen(L, -1)); + + for (int slice = 0; slice < slicelen; slice++) + { + lua_rawgeti(L, -1, mip + 1); + + auto data = getImageData(L, -1, true, slice == 0 && mip == 0 ? &settings.pixeldensity : nullptr); + if (data.first.get()) + slices.set(slice, mip, data.first); + else + slices.set(slice, mip, data.second->getSlice(0, 0)); + + lua_pop(L, 1); + } + } + } + else + { + for (int layer = 0; layer < tlen; layer++) + { + lua_rawgeti(L, 1, layer + 1); + auto data = getImageData(L, -1, true, layer == 0 ? &settings.pixeldensity : nullptr); + if (data.first.get()) + slices.set(layer, 0, data.first); + else + slices.add(data.second, layer, 0, false, true); + } + } + + lua_pop(L, tlen); + } + else + { + auto data = getImageData(L, 1, true, &settings.pixeldensity); + if (data.first.get()) + slices.set(0, 0, data.first); + else + slices.add(data.second, 0, 0, true, true); + } + + settings = w__optImageSettings(L, 2, settings); + return w__pushNewImage(L, slices, settings); +} + int w_newImage(lua_State *L) { luax_checkgraphicscreated(L); - std::vector data; - std::vector cdata; - + Image::Slices slices(TEXTURE_2D); Image::Settings settings; - bool releasedata = false; - - // Convert to ImageData / CompressedImageData, if necessary. - if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type)) + if (lua_istable(L, 1)) { - auto imagemodule = Module::getInstance(Module::M_IMAGE); - if (imagemodule == nullptr) - return luaL_error(L, "Cannot load images without the love.image module."); - - love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1); - - // Parse a density scale of 2.0 from "image@2x.png". - const std::string &fname = fdata->getName(); - size_t namelen = fname.length(); - size_t atpos = fname.rfind('@'); - - if (atpos != std::string::npos && atpos + 2 < namelen - && (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X')) + int n = std::max(1, (int) luax_objlen(L, 1)); + for (int i = 0; i < n; i++) { - char *end = nullptr; - long density = strtol(fname.c_str() + atpos + 1, &end, 10); - if (end != nullptr && density > 0) - settings.pixeldensity = (float) density; + lua_rawgeti(L, 1, i + 1); + auto data = getImageData(L, -1, true, i == 0 ? &settings.pixeldensity : nullptr); + if (data.first.get()) + slices.set(0, i, data.first); + else + slices.set(0, i, data.second->getSlice(0, 0)); } - - if (imagemodule->isCompressed(fdata)) - { - luax_catchexcept(L, - [&]() { cdata.push_back(imagemodule->newCompressedData(fdata)); }, - [&](bool) { fdata->release(); } - ); - } - else - { - luax_catchexcept(L, - [&]() { data.push_back(imagemodule->newImageData(fdata)); }, - [&](bool) { fdata->release(); } - ); - } - - // Lua's GC won't release the image data, so we should do it ourselves. - releasedata = true; + lua_pop(L, n); } - else if (luax_istype(L, 1, love::image::CompressedImageData::type)) - cdata.push_back(love::image::luax_checkcompressedimagedata(L, 1)); else - data.push_back(love::image::luax_checkimagedata(L, 1)); - - if (!lua_isnoneornil(L, 2)) { - luaL_checktype(L, 2, LUA_TTABLE); - - settings.mipmaps = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS), settings.mipmaps); - settings.linear = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_LINEAR), settings.linear); - settings.pixeldensity = (float) luax_numberflag(L, 2, luax_imageSettingName(Image::SETTING_PIXELDENSITY), settings.pixeldensity); - - lua_getfield(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS)); - - // Add all manually specified mipmap images to the array of imagedata. - // i.e. settings = {mipmaps = {mip1, mip2, ...}}. - if (lua_istable(L, -1)) - { - for (size_t i = 1; i <= luax_objlen(L, -1); i++) - { - lua_rawgeti(L, -1, i); - - if (!data.empty()) - { - if (!luax_istype(L, -1, love::image::ImageData::type)) - luax_convobj(L, -1, "image", "newImageData"); - - data.push_back(love::image::luax_checkimagedata(L, -1)); - } - else if (!cdata.empty()) - { - if (!luax_istype(L, -1, love::image::CompressedImageData::type)) - luax_convobj(L, -1, "image", "newCompressedData"); - - cdata.push_back(love::image::luax_checkcompressedimagedata(L, -1)); - } - - lua_pop(L, 1); - } - } - - lua_pop(L, 1); + auto data = getImageData(L, 1, true, &settings.pixeldensity); + if (data.first.get()) + slices.set(0, 0, data.first); + else + slices.add(data.second, 0, 0, false, true); } - // Create the image. - Image *image = nullptr; - luax_catchexcept(L, - [&]() { - if (!cdata.empty()) - image = instance()->newImage(cdata, settings); - else if (!data.empty()) - image = instance()->newImage(data, settings); - }, - [&](bool) { - if (releasedata) - { - for (auto d : data) - d->release(); - for (auto d : cdata) - d->release(); - } - } - ); - - if (image == nullptr) - return luaL_error(L, "Could not load image."); - - // Push the type. - luax_pushtype(L, image); - image->release(); - return 1; + settings = w__optImageSettings(L, 2, settings); + return w__pushNewImage(L, slices, settings); } int w_newQuad(lua_State *L) @@ -605,18 +882,6 @@ int w_newImageFont(lua_State *L) // filter for glyphs Texture::Filter filter = instance()->getDefaultFilter(); - // Convert to ImageData if necessary. - if (luax_istype(L, 1, Image::type)) - { - Image *i = luax_checktype(L, 1); - filter = i->getFilter(); - const auto &idlevels = i->getImageData(); - if (idlevels.empty()) - return luaL_argerror(L, 1, "Image must not be compressed."); - luax_pushtype(L, idlevels[0].get()); - lua_replace(L, 1); - } - // Convert to Rasterizer if necessary. if (!luax_istype(L, 1, love::font::Rasterizer::type)) { @@ -687,35 +952,50 @@ int w_newCanvas(lua_State *L) { luax_checkgraphicscreated(L); - // check if width and height are given. else default to screen dimensions. - int width = (int) luaL_optnumber(L, 1, instance()->getWidth()); - int height = (int) luaL_optnumber(L, 2, instance()->getHeight()); - Canvas::Settings settings; + // check if width and height are given. else default to screen dimensions. + settings.width = (int) luaL_optnumber(L, 1, instance()->getWidth()); + settings.height = (int) luaL_optnumber(L, 2, instance()->getHeight()); + // Default to the screen's current pixel density scale. settings.pixeldensity = instance()->getScreenPixelDensity(); - if (!lua_isnoneornil(L, 3)) + int startidx = 3; + + if (lua_isnumber(L, 3)) { - lua_getfield(L, 3, "format"); + settings.layers = (int) luaL_checknumber(L, 3); + settings.type = TEXTURE_2D_ARRAY; + startidx = 4; + } + + if (!lua_isnoneornil(L, startidx)) + { + settings.pixeldensity = (float) luax_numberflag(L, startidx, "pixeldensity", settings.pixeldensity); + settings.msaa = luax_intflag(L, startidx, "msaa", 0); + + lua_getfield(L, startidx, "format"); if (!lua_isnoneornil(L, -1)) { const char *str = luaL_checkstring(L, -1); if (!getConstant(str, settings.format)) - return luaL_error(L, "Invalid Canvas format: %s", str); + return luaL_error(L, "Invalid pixel format: %s", str); } lua_pop(L, 1); - settings.pixeldensity = (float) luax_numberflag(L, 3, "pixeldensity", settings.pixeldensity); - settings.msaa = luax_intflag(L, 3, "msaa", settings.msaa); + lua_getfield(L, startidx, "type"); + if (!lua_isnoneornil(L, -1)) + { + const char *str = luaL_checkstring(L, -1); + if (!Texture::getConstant(str, settings.type)) + return luaL_error(L, "Invalid texture type: %s", str); + } + lua_pop(L, 1); } Canvas *canvas = nullptr; - luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(width, height, settings); }); - - if (canvas == nullptr) - return luaL_error(L, "Canvas not created, but no error thrown. I don't even..."); + luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(settings); }); luax_pushtype(L, canvas); canvas->release(); @@ -2178,6 +2458,9 @@ static const luaL_Reg functions[] = { "present", w_present }, { "newImage", w_newImage }, + { "newArrayImage", w_newArrayImage }, + { "newVolumeImage", w_newVolumeImage }, + { "newCubeImage", w_newCubeImage }, { "newQuad", w_newQuad }, { "newFont", w_newFont }, { "newImageFont", w_newImageFont }, diff --git a/src/modules/graphics/wrap_Graphics.lua b/src/modules/graphics/wrap_Graphics.lua index 742682243..b42408960 100644 --- a/src/modules/graphics/wrap_Graphics.lua +++ b/src/modules/graphics/wrap_Graphics.lua @@ -42,7 +42,17 @@ GLSL.SYNTAX = [[ #endif #define number float #define Image sampler2D -#define extern uniform]] +#define ArrayImage sampler2DArray +#define CubeImage samplerCube +#define VolumeImage sampler3D +#define extern uniform +#ifdef GL_EXT_texture_array +#extension GL_EXT_texture_array : enable +#endif +#ifdef GL_OES_texture_3D +#extension GL_OES_texture_3D : enable +#endif +]] -- Uniforms shared by the vertex and pixel shader stages. GLSL.UNIFORMS = [[ @@ -66,13 +76,36 @@ GLSL.FUNCTIONS = [[ #else #if __VERSION__ >= 130 #define texture2D Texel + #define texture3D Texel + #define textureCube Texel + #define texture2DArray Texel #define love_texture2D texture + #define love_texture3D texture + #define love_textureCube texture + #define love_texture2DArray texture #else #define love_texture2D texture2D + #define love_texture3D texture3D + #define love_textureCube textureCube + #define love_texture2DArray texture2DArray #endif vec4 Texel(sampler2D s, vec2 c) { return love_texture2D(s, c); } + vec4 Texel(samplerCube s, vec3 c) { return love_textureCube(s, c); } + #if __VERSION__ > 100 || defined(GL_OES_texture_3D) + vec4 Texel(sampler3D s, vec3 c) { return love_texture3D(s, c); } + #endif + #if __VERSION__ >= 130 || defined(GL_EXT_texture_array) + vec4 Texel(sampler2DArray s, vec3 c) { return love_texture2DArray(s, c); } + #endif #ifdef PIXEL vec4 Texel(sampler2D s, vec2 c, float b) { return love_texture2D(s, c, b); } + vec4 Texel(samplerCube s, vec3 c, float b) { return love_textureCube(s, c, b); } + #if __VERSION__ > 100 || defined(GL_OES_texture_3D) + vec4 Texel(sampler3D s, vec3 c, float b) { return love_texture3D(s, c, b); } + #endif + #if __VERSION__ >= 130 || defined(GL_EXT_texture_array) + vec4 Texel(sampler2DArray s, vec3 c, float b) { return love_texture2DArray(s, c, b); } + #endif #endif #define texture love_texture #endif @@ -187,17 +220,11 @@ GLSL.PIXEL = { #if __VERSION__ >= 130 #define varying in - #ifdef LOVE_MULTI_CANVAS - layout(location = 0) out vec4 love_Canvases[love_MaxCanvases]; - #else - layout(location = 0) out vec4 love_PixelColor; - #endif + layout(location = 0) out vec4 love_Canvases[love_MaxCanvases]; + #define love_PixelColor love_Canvases[0] #else - #ifdef LOVE_MULTI_CANVAS - #define love_Canvases gl_FragData - #else - #define love_PixelColor gl_FragColor - #endif + #define love_Canvases gl_FragData + #define love_PixelColor gl_FragColor #endif // See Shader::updateScreenParams in Shader.cpp. @@ -259,7 +286,6 @@ local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacor "#define "..stage, glsl1on3 and "#define LOVE_GLSL1_ON_GLSL3 1" or "", gammacorrect and "#define LOVE_GAMMA_CORRECT 1" or "", - multicanvas and "#define LOVE_MULTI_CANVAS 1" or "", GLSL.SYNTAX, GLSL[stage].HEADER, GLSL.UNIFORMS, diff --git a/src/modules/graphics/wrap_Image.cpp b/src/modules/graphics/wrap_Image.cpp index ea31fc9d1..b25f7b2ff 100644 --- a/src/modules/graphics/wrap_Image.cpp +++ b/src/modules/graphics/wrap_Image.cpp @@ -31,40 +31,11 @@ Image *luax_checkimage(lua_State *L, int idx) return luax_checktype(L, idx); } -int w_Image_setMipmapFilter(lua_State *L) +int w_Image_isFormatLinear(lua_State *L) { - Image *t = luax_checkimage(L, 1); - Texture::Filter f = t->getFilter(); - - if (lua_isnoneornil(L, 2)) - f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given - else - { - const char *mipmapstr = luaL_checkstring(L, 2); - if (!Texture::getConstant(mipmapstr, f.mipmap)) - return luaL_error(L, "Invalid filter mode: %s", mipmapstr); - } - - luax_catchexcept(L, [&](){ t->setFilter(f); }); - t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0)); - - return 0; -} - -int w_Image_getMipmapFilter(lua_State *L) -{ - Image *t = luax_checkimage(L, 1); - - const Texture::Filter &f = t->getFilter(); - - const char *mipmapstr; - if (Texture::getConstant(f.mipmap, mipmapstr)) - lua_pushstring(L, mipmapstr); - else - lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled - - lua_pushnumber(L, t->getMipmapSharpness()); - return 2; + Image *i = luax_checkimage(L, 1); + luax_pushboolean(L, i->isFormatLinear()); + return 1; } int w_Image_isCompressed(lua_State *L) @@ -74,78 +45,33 @@ int w_Image_isCompressed(lua_State *L) return 1; } -int w_Image_refresh(lua_State *L) +int w_Image_replacePixels(lua_State *L) { Image *i = luax_checkimage(L, 1); + love::image::ImageData *id = luax_checktype(L, 2); - int xoffset = (int) luaL_optnumber(L, 2, 0); - int yoffset = (int) luaL_optnumber(L, 3, 0); - int w = (int) luaL_optnumber(L, 4, i->getWidth()); - int h = (int) luaL_optnumber(L, 5, i->getHeight()); + int slice = 0; + int mipmap = 0; + bool reloadmipmaps = i->getMipmapsType() == Image::MIPMAPS_GENERATED; - luax_catchexcept(L, [&](){ i->refresh(xoffset, yoffset, w, h); }); + if (i->getTextureType() != TEXTURE_2D) + { + slice = (int) luaL_checknumber(L, 3) - 1; + if (!reloadmipmaps) + mipmap = (int) luaL_optnumber(L, 4, 1) - 1; + } + else if (!reloadmipmaps) + mipmap = (int) luaL_optnumber(L, 3, 1) - 1; + + luax_catchexcept(L, [&](){ i->replacePixels(id, slice, mipmap, reloadmipmaps); }); return 0; } -int w_Image_getData(lua_State *L) -{ - Image *i = luax_checkimage(L, 1); - int n = 0; - - if (i->isCompressed()) - { - for (const auto &cdata : i->getCompressedData()) - { - luax_pushtype(L, cdata.get()); - n++; - } - } - else - { - for (const auto &data : i->getImageData()) - { - luax_pushtype(L, data.get()); - n++; - } - } - - return n; -} - -const char *luax_imageSettingName(Image::SettingType settingtype) -{ - const char *name = nullptr; - Image::getConstant(settingtype, name); - return name; -} - -int w_Image_getFlags(lua_State *L) -{ - Image *i = luax_checkimage(L, 1); - Image::Settings settings = i->getFlags(); - - lua_createtable(L, 0, 2); - - lua_pushboolean(L, settings.mipmaps); - lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_MIPMAPS)); - - lua_pushboolean(L, settings.linear); - lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_LINEAR)); - - lua_pushnumber(L, settings.pixeldensity); - lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_PIXELDENSITY)); - - return 1; -} - static const luaL_Reg w_Image_functions[] = { - { "setMipmapFilter", w_Image_setMipmapFilter }, - { "getMipmapFilter", w_Image_getMipmapFilter }, + { "isFormatLinear", w_Image_isFormatLinear }, { "isCompressed", w_Image_isCompressed }, - { "refresh", w_Image_refresh }, - { "getData", w_Image_getData }, - { "getFlags", w_Image_getFlags }, + { "replacePixels", w_Image_replacePixels }, { 0, 0 } }; diff --git a/src/modules/graphics/wrap_Image.h b/src/modules/graphics/wrap_Image.h index 74d7b79e3..0900be2c8 100644 --- a/src/modules/graphics/wrap_Image.h +++ b/src/modules/graphics/wrap_Image.h @@ -30,7 +30,6 @@ namespace love namespace graphics { -const char *luax_imageSettingName(Image::SettingType settingtype); Image *luax_checkimage(lua_State *L, int idx); extern "C" int luaopen_image(lua_State *L); diff --git a/src/modules/graphics/wrap_Mesh.cpp b/src/modules/graphics/wrap_Mesh.cpp index f6122b394..a5326c24e 100644 --- a/src/modules/graphics/wrap_Mesh.cpp +++ b/src/modules/graphics/wrap_Mesh.cpp @@ -456,7 +456,7 @@ int w_Mesh_setTexture(lua_State *L) else { Texture *tex = luax_checktexture(L, 2); - t->setTexture(tex); + luax_catchexcept(L, [&](){ t->setTexture(tex); }); } return 0; diff --git a/src/modules/graphics/wrap_ParticleSystem.cpp b/src/modules/graphics/wrap_ParticleSystem.cpp index c2ec31839..a98e5ccaf 100644 --- a/src/modules/graphics/wrap_ParticleSystem.cpp +++ b/src/modules/graphics/wrap_ParticleSystem.cpp @@ -55,7 +55,7 @@ int w_ParticleSystem_setTexture(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); Texture *tex = luax_checktexture(L, 2); - t->setTexture(tex); + luax_catchexcept(L, [&](){ t->setTexture(tex); }); return 0; } diff --git a/src/modules/graphics/wrap_Shader.cpp b/src/modules/graphics/wrap_Shader.cpp index 7d457bb08..fb48b4e02 100644 --- a/src/modules/graphics/wrap_Shader.cpp +++ b/src/modules/graphics/wrap_Shader.cpp @@ -267,7 +267,12 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad textures.reserve(count); for (int i = 0; i < count; i++) - textures.push_back(luax_checktexture(L, startidx + i)); + { + Texture *tex = luax_checktexture(L, startidx + i); + if (tex->getTextureType() != info->textureType) + return luaL_argerror(L, startidx + i, "invalid texture type for uniform"); + textures.push_back(tex); + } luax_catchexcept(L, [&]() { shader->sendTextures(info, textures.data(), count); }); return 0; diff --git a/src/modules/graphics/wrap_SpriteBatch.cpp b/src/modules/graphics/wrap_SpriteBatch.cpp index 85c7bd0e7..fa30a221c 100644 --- a/src/modules/graphics/wrap_SpriteBatch.cpp +++ b/src/modules/graphics/wrap_SpriteBatch.cpp @@ -120,7 +120,7 @@ int w_SpriteBatch_setTexture(lua_State *L) { SpriteBatch *t = luax_checkspritebatch(L, 1); Texture *tex = luax_checktexture(L, 2); - t->setTexture(tex); + luax_catchexcept(L, [&](){ t->setTexture(tex); }); return 0; } diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index fe3fa6261..9f2d23bdf 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -30,6 +30,16 @@ Texture *luax_checktexture(lua_State *L, int idx) return luax_checktype(L, idx); } +int w_Texture_getTextureType(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + const char *tstr; + if (!Texture::getConstant(t->getTextureType(), tstr)) + return luaL_error(L, "unknown texture type"); + lua_pushstring(L, tstr); + return 1; +} + int w_Texture_getWidth(lua_State *L) { Texture *t = luax_checktexture(L, 1); @@ -52,6 +62,27 @@ int w_Texture_getDimensions(lua_State *L) return 2; } +int w_Texture_getDepth(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + lua_pushnumber(L, t->getDepth()); + return 1; +} + +int w_Texture_getLayerCount(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + lua_pushnumber(L, t->getLayerCount()); + return 1; +} + +int w_Texture_getMipmapCount(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + lua_pushnumber(L, t->getMipmapCount()); + return 1; +} + int w_Texture_getPixelWidth(lua_State *L) { Texture *t = luax_checktexture(L, 1); @@ -119,6 +150,42 @@ int w_Texture_getFilter(lua_State *L) return 3; } +int w_Texture_setMipmapFilter(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + Texture::Filter f = t->getFilter(); + + if (lua_isnoneornil(L, 2)) + f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given + else + { + const char *mipmapstr = luaL_checkstring(L, 2); + if (!Texture::getConstant(mipmapstr, f.mipmap)) + return luaL_error(L, "Invalid filter mode: %s", mipmapstr); + } + + luax_catchexcept(L, [&](){ t->setFilter(f); }); + t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0)); + + return 0; +} + +int w_Texture_getMipmapFilter(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + + const Texture::Filter &f = t->getFilter(); + + const char *mipmapstr; + if (Texture::getConstant(f.mipmap, mipmapstr)) + lua_pushstring(L, mipmapstr); + else + lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled + + lua_pushnumber(L, t->getMipmapSharpness()); + return 2; +} + int w_Texture_setWrap(lua_State *L) { Texture *t = luax_checktexture(L, 1); @@ -143,30 +210,53 @@ int w_Texture_getWrap(lua_State *L) const char *sstr = nullptr; const char *tstr = nullptr; + const char *rstr = nullptr; if (!Texture::getConstant(w.s, sstr)) return luaL_error(L, "Unknown wrap mode."); if (!Texture::getConstant(w.t, tstr)) return luaL_error(L, "Unknown wrap mode."); + if (!Texture::getConstant(w.r, rstr)) + return luaL_error(L, "Unknown wrap mode."); lua_pushstring(L, sstr); lua_pushstring(L, tstr); + lua_pushstring(L, rstr); return 2; } +int w_Texture_getFormat(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + PixelFormat format = t->getPixelFormat(); + const char *str; + if (!getConstant(format, str)) + return luaL_error(L, "Unknown pixel format."); + + lua_pushstring(L, str); + return 1; +} + const luaL_Reg w_Texture_functions[] = { + { "getTextureType", w_Texture_getTextureType }, { "getWidth", w_Texture_getWidth }, { "getHeight", w_Texture_getHeight }, { "getDimensions", w_Texture_getDimensions }, + { "getDepth", w_Texture_getDepth }, + { "getLayerCount", w_Texture_getLayerCount }, + { "getMipmapCount", w_Texture_getMipmapCount }, { "getPixelWidth", w_Texture_getPixelWidth }, { "getPixelHeight", w_Texture_getPixelHeight }, { "getPixelDimensions", w_Texture_getPixelDimensions }, { "getPixelDensity", w_Texture_getPixelDensity }, { "setFilter", w_Texture_setFilter }, { "getFilter", w_Texture_getFilter }, + { "setMipmapFilter", w_Texture_setMipmapFilter }, + { "getMipmapFilter", w_Texture_getMipmapFilter }, { "setWrap", w_Texture_setWrap }, { "getWrap", w_Texture_getWrap }, + { "getFormat", w_Texture_getFormat }, { 0, 0 } }; diff --git a/src/modules/image/CompressedImageData.cpp b/src/modules/image/CompressedImageData.cpp index 7323e288e..066a3b291 100644 --- a/src/modules/image/CompressedImageData.cpp +++ b/src/modules/image/CompressedImageData.cpp @@ -27,11 +27,57 @@ namespace image love::Type CompressedImageData::type("CompressedImageData", &Data::type); +CompressedImageData::Memory::Memory(size_t size) + : data(nullptr) + , size(size) +{ + try + { + data = new uint8[size]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); + } +} + +CompressedImageData::Memory::~Memory() +{ + delete[] data; +} + +CompressedImageData::Slice::Slice(PixelFormat format, int width, int height, Memory *memory, size_t offset, size_t size) + : memory(memory) + , offset(offset) + , dataSize(size) +{ + this->format = format; + this->width = width; + this->height = height; +} + +CompressedImageData::Slice::Slice(const Slice &s) + : memory(s.memory) + , offset(s.offset) + , dataSize(s.dataSize) +{ + this->format = s.getFormat(); + this->width = s.getWidth(); + this->height = s.getHeight(); +} + +CompressedImageData::Slice::~Slice() +{ +} + +CompressedImageData::Slice *CompressedImageData::Slice::clone() const +{ + return new Slice(*this); +} + CompressedImageData::CompressedImageData() : format(PIXELFORMAT_UNKNOWN) , sRGB(false) - , data(nullptr) - , dataSize(0) { } @@ -41,45 +87,50 @@ CompressedImageData::~CompressedImageData() size_t CompressedImageData::getSize() const { - return dataSize; + return memory->size; } void *CompressedImageData::getData() const { - return data; + return memory->data; } -int CompressedImageData::getMipmapCount() const +int CompressedImageData::getMipmapCount(int /*slice*/) const { return (int) dataImages.size(); } +int CompressedImageData::getSliceCount(int /*mip*/) const +{ + return 1; +} + size_t CompressedImageData::getSize(int miplevel) const { - checkMipmapLevelExists(miplevel); + checkSliceExists(0, miplevel); - return dataImages[miplevel].size; + return dataImages[miplevel]->getSize(); } void *CompressedImageData::getData(int miplevel) const { - checkMipmapLevelExists(miplevel); + checkSliceExists(0, miplevel); - return &dataImages[miplevel].data[0]; + return dataImages[miplevel]->getData(); } int CompressedImageData::getWidth(int miplevel) const { - checkMipmapLevelExists(miplevel); + checkSliceExists(0, miplevel); - return dataImages[miplevel].width; + return dataImages[miplevel]->getWidth(); } int CompressedImageData::getHeight(int miplevel) const { - checkMipmapLevelExists(miplevel); + checkSliceExists(0, miplevel); - return dataImages[miplevel].height; + return dataImages[miplevel]->getHeight(); } PixelFormat CompressedImageData::getFormat() const @@ -92,8 +143,18 @@ bool CompressedImageData::isSRGB() const return sRGB; } -void CompressedImageData::checkMipmapLevelExists(int miplevel) const +CompressedImageData::Slice *CompressedImageData::getSlice(int slice, int miplevel) const { + checkSliceExists(slice, miplevel); + + return dataImages[miplevel].get(); +} + +void CompressedImageData::checkSliceExists(int slice, int miplevel) const +{ + if (slice != 0) + throw love::Exception("Slice index %d does not exists", slice + 1); + if (miplevel < 0 || miplevel >= (int) dataImages.size()) throw love::Exception("Mipmap level %d does not exist", miplevel + 1); } diff --git a/src/modules/image/CompressedImageData.h b/src/modules/image/CompressedImageData.h index dd45d133f..fef680085 100644 --- a/src/modules/image/CompressedImageData.h +++ b/src/modules/image/CompressedImageData.h @@ -18,14 +18,14 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#ifndef LOVE_IMAGE_COMPRESSED_IMAGE_DATA_H -#define LOVE_IMAGE_COMPRESSED_IMAGE_DATA_H +#pragma once // LOVE #include "common/Data.h" #include "common/StringMap.h" #include "common/int.h" #include "common/pixelformat.h" +#include "ImageDataBase.h" // STL #include @@ -44,16 +44,45 @@ class CompressedImageData : public Data { public: - static love::Type type; + class Memory : public Object + { + public: + + Memory(size_t size); + virtual ~Memory(); + + uint8 *data; + size_t size; + + }; // Memory // Compressed image data can have multiple mipmap levels, each represented // by a sub-image. - struct SubImage + class Slice : public ImageDataBase { - int width, height; - size_t size; - uint8 *data; // Should not have ownership of the data. - }; + public: + + Slice(PixelFormat format, int width, int height, Memory *memory, size_t offset, size_t size); + Slice(const Slice &slice); + virtual ~Slice(); + + Slice *clone() const override; + void *getData() const override { return memory->data + offset; } + size_t getSize() const override { return dataSize; } + bool isSRGB() const override { return sRGB; } + size_t getOffset() const { return offset; } + + private: + + StrongRef memory; + + size_t offset; + size_t dataSize; + bool sRGB; + + }; // Slice + + static love::Type type; CompressedImageData(); virtual ~CompressedImageData(); @@ -67,7 +96,12 @@ public: * Gets the number of mipmaps in this Compressed Image Data. * Includes the base image level. **/ - int getMipmapCount() const; + int getMipmapCount(int slice = 0) const; + + /** + * Gets the number of slices (array layers, cube faces, 3D layers, etc.) + **/ + int getSliceCount(int mip = 0) const; /** * Gets the size in bytes of a sub-image at the specified mipmap level. @@ -96,6 +130,8 @@ public: bool isSRGB() const; + Slice *getSlice(int slice, int miplevel) const; + protected: PixelFormat format; @@ -103,17 +139,14 @@ protected: bool sRGB; // Single block of memory containing all of the sub-images. - uint8 *data; - size_t dataSize; + StrongRef memory; // Texture info for each mipmap level. - std::vector dataImages; + std::vector> dataImages; - void checkMipmapLevelExists(int miplevel) const; + void checkSliceExists(int slice, int miplevel) const; }; // CompressedImageData } // image } // love - -#endif // LOVE_IMAGE_COMPRESSED_IMAGE_DATA_H diff --git a/src/modules/image/Image.cpp b/src/modules/image/Image.cpp index cf194c451..9e43e48f6 100644 --- a/src/modules/image/Image.cpp +++ b/src/modules/image/Image.cpp @@ -28,5 +28,92 @@ namespace image love::Type Image::type("image", &Module::type); +ImageData *Image::newPastedImageData(ImageData *src, int sx, int sy, int w, int h) +{ + ImageData *res = newImageData(w, h, src->getFormat()); + try + { + res->paste(src, 0, 0, sx, sy, w, h); + } + catch (love::Exception &) + { + res->release(); + throw; + } + return res; +} + +std::vector> Image::newCubeFaces(love::image::ImageData *src) +{ + // The faces array is always ordered +x, -x, +y, -y, +z, -z. + std::vector> faces; + + int totalW = src->getWidth(); + int totalH = src->getHeight(); + + if (totalW % 3 == 0 && totalH % 4 == 0 && totalW / 3 == totalH / 4) + { + // +y + // +z +x -z + // -y + // -x + + int w = totalW / 3; + int h = totalH / 4; + + faces.emplace_back(newPastedImageData(src, 1*w, 1*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 1*w, 3*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 1*w, 0*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 1*w, 2*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 0*w, 1*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 2*w, 1*h, w, h), Acquire::NORETAIN); + } + else if (totalW % 4 == 0 && totalH % 3 == 0 && totalW / 4 == totalH / 3) + { + // +y + // -x +z +x -z + // -y + + int w = totalW / 4; + int h = totalH / 3; + + faces.emplace_back(newPastedImageData(src, 2*w, 1*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 0*w, 1*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 1*w, 0*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 1*w, 2*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 1*w, 1*h, w, h), Acquire::NORETAIN); + faces.emplace_back(newPastedImageData(src, 3*w, 1*h, w, h), Acquire::NORETAIN); + } + else if (totalH % 6 == 0 && totalW == totalH / 6) + { + // +x + // -x + // +y + // -y + // +z + // -z + + int w = totalW; + int h = totalH / 6; + + for (int i = 0; i < 6; i++) + faces.emplace_back(newPastedImageData(src, 0, i * h, w, h), Acquire::NORETAIN); + } + else if (totalW % 6 == 0 && totalW / 6 == totalH) + { + // +x -x +y -y +z -z + + int w = totalW / 6; + int h = totalH; + + for (int i = 0; i < 6; i++) + faces.emplace_back(newPastedImageData(src, i * w, 0, w, h), Acquire::NORETAIN); + } + else + throw love::Exception("Unknown cubemap image dimensions!"); + + return faces; +} + } // image } // love diff --git a/src/modules/image/Image.h b/src/modules/image/Image.h index 57193c9f2..eac85743f 100644 --- a/src/modules/image/Image.h +++ b/src/modules/image/Image.h @@ -90,6 +90,12 @@ public: **/ virtual bool isCompressed(love::filesystem::FileData *data) = 0; + std::vector> newCubeFaces(ImageData *layoutData); + +private: + + ImageData *newPastedImageData(ImageData *src, int sx, int sy, int w, int h); + }; // Image } // image diff --git a/src/modules/image/ImageData.cpp b/src/modules/image/ImageData.cpp index cd8b42281..a1ed35ace 100644 --- a/src/modules/image/ImageData.cpp +++ b/src/modules/image/ImageData.cpp @@ -30,10 +30,7 @@ namespace image love::Type ImageData::type("ImageData", &Data::type); ImageData::ImageData() - : format(PIXELFORMAT_UNKNOWN) - , width(0) - , height(0) - , data(nullptr) + : data(nullptr) { } @@ -51,26 +48,16 @@ void *ImageData::getData() const return data; } +bool ImageData::isSRGB() const +{ + return false; +} + bool ImageData::inside(int x, int y) const { return x >= 0 && x < getWidth() && y >= 0 && y < getHeight(); } -PixelFormat ImageData::getFormat() const -{ - return format; -} - -int ImageData::getWidth() const -{ - return width; -} - -int ImageData::getHeight() const -{ - return height; -} - void ImageData::setPixel(int x, int y, const Pixel &p) { if (!inside(x, y)) diff --git a/src/modules/image/ImageData.h b/src/modules/image/ImageData.h index c2ee80a8c..3bd94b9ce 100644 --- a/src/modules/image/ImageData.h +++ b/src/modules/image/ImageData.h @@ -18,8 +18,7 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#ifndef LOVE_IMAGE_IMAGE_DATA_H -#define LOVE_IMAGE_IMAGE_DATA_H +#pragma once // LOVE #include "common/Data.h" @@ -29,6 +28,7 @@ #include "common/halffloat.h" #include "filesystem/FileData.h" #include "thread/threads.h" +#include "ImageDataBase.h" using love::thread::Mutex; @@ -55,7 +55,7 @@ union Pixel /** * Represents raw pixel data. **/ -class ImageData : public Data +class ImageData : public ImageDataBase { public: @@ -71,8 +71,6 @@ public: ImageData(); virtual ~ImageData(); - PixelFormat getFormat() const; - /** * Paste part of one ImageData onto another. The subregion defined by the top-left * corner (sx, sy) and the size (sw,sh) will be pasted to (dx,dy) in this ImageData. @@ -92,16 +90,6 @@ public: **/ bool inside(int x, int y) const; - /** - * Gets the width of this ImageData. - **/ - int getWidth() const; - - /** - * Gets the height of this ImageData. - **/ - int getHeight() const; - /** * Sets the pixel at location (x,y). * @param x The location along the x-axis. @@ -127,10 +115,11 @@ public: love::thread::Mutex *getMutex() const; - // Implements Data. - virtual ImageData *clone() const = 0; - virtual void *getData() const; - virtual size_t getSize() const; + // Implements ImageDataBase. + virtual ImageData *clone() const override = 0; + void *getData() const override; + size_t getSize() const override; + bool isSRGB() const override; size_t getPixelSize() const; @@ -141,14 +130,6 @@ public: protected: - PixelFormat format; - - // The width of the image data. - int width; - - // The height of the image data. - int height; - // The actual data. unsigned char *data; @@ -166,5 +147,3 @@ private: } // image } // love - -#endif // LOVE_IMAGE_IMAGE_DATA_H diff --git a/src/modules/graphics/opengl/Font.h b/src/modules/image/ImageDataBase.cpp similarity index 58% rename from src/modules/graphics/opengl/Font.h rename to src/modules/image/ImageDataBase.cpp index fc15cfb66..2cba195e3 100644 --- a/src/modules/graphics/opengl/Font.h +++ b/src/modules/image/ImageDataBase.cpp @@ -18,45 +18,34 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#pragma once - -// LOVE -#include "graphics/Font.h" -#include "graphics/Volatile.h" -#include "OpenGL.h" +#include "ImageDataBase.h" namespace love { -namespace graphics -{ -namespace opengl +namespace image { -class Font final : public love::graphics::Font, public Volatile +ImageDataBase::ImageDataBase() + : format(PIXELFORMAT_UNKNOWN) + , width(0) + , height(0) { -public: +} - Font(love::font::Rasterizer *r, const Texture::Filter &filter); - virtual ~Font(); +PixelFormat ImageDataBase::getFormat() const +{ + return format; +} - void setFilter(const Texture::Filter &f) override; +int ImageDataBase::getWidth() const +{ + return width; +} - // Implements Volatile. - bool loadVolatile() override; - void unloadVolatile() override; +int ImageDataBase::getHeight() const +{ + return height; +} -private: - - void createTexture() override; - void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) override; - - // vector of packed textures - std::vector textures; - - size_t textureMemorySize; - -}; // Font - -} // opengl -} // graphics +} // image } // love diff --git a/src/modules/graphics/opengl/Video.h b/src/modules/image/ImageDataBase.h similarity index 64% rename from src/modules/graphics/opengl/Video.h rename to src/modules/image/ImageDataBase.h index 9b43b7b73..cf547afcd 100644 --- a/src/modules/graphics/opengl/Video.h +++ b/src/modules/image/ImageDataBase.h @@ -21,38 +21,35 @@ #pragma once // LOVE -#include "graphics/Video.h" -#include "graphics/Volatile.h" -#include "OpenGL.h" +#include "common/Data.h" +#include "common/pixelformat.h" namespace love { -namespace graphics -{ -namespace opengl +namespace image { -class Video : public love::graphics::Video, public Volatile +class ImageDataBase : public Data { public: - Video(love::video::VideoStream *stream, float pixeldensity = 1.0f); - virtual ~Video(); + ImageDataBase(); + virtual ~ImageDataBase() {} - // Volatile - bool loadVolatile() override; - void unloadVolatile() override; + PixelFormat getFormat() const; - void setFilter(const Texture::Filter &f) override; + int getWidth() const; + int getHeight() const; -private: + virtual bool isSRGB() const = 0; - void uploadFrame(const love::video::VideoStream::Frame *frame) override; +protected: - Texture::Filter filter; + PixelFormat format; + int width; + int height; -}; // Video +}; // ImageDataBase -} // opengl -} // graphics +} // image } // love diff --git a/src/modules/image/magpie/ASTCHandler.cpp b/src/modules/image/magpie/ASTCHandler.cpp index a8fc3a006..63fb7fe95 100644 --- a/src/modules/image/magpie/ASTCHandler.cpp +++ b/src/modules/image/magpie/ASTCHandler.cpp @@ -104,7 +104,7 @@ bool ASTCHandler::canParse(const filesystem::FileData *data) return true; } -uint8 *ASTCHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB) +StrongRef ASTCHandler::parse(filesystem::FileData *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParse(filedata)) throw love::Exception("Could not decode compressed data (not an .astc file?)"); @@ -129,35 +129,17 @@ uint8 *ASTCHandler::parse(filesystem::FileData *filedata, std::vector filedata->getSize()) throw love::Exception("Could not parse .astc file: file is too small."); - uint8 *data = nullptr; - - try - { - data = new uint8[totalsize]; - } - catch (std::bad_alloc &) - { - throw love::Exception("Out of memory."); - } + StrongRef memory(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN); // .astc files only store a single mipmap level. - memcpy(data, (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize); + memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize); - CompressedImageData::SubImage mip; + images.emplace_back(new CompressedImageData::Slice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN); - mip.width = sizeX; - mip.height = sizeY; - - mip.size = totalsize; - mip.data = data; - - images.push_back(mip); - - dataSize = totalsize; format = cformat; sRGB = false; - return data; + return memory; } } // magpie diff --git a/src/modules/image/magpie/ASTCHandler.h b/src/modules/image/magpie/ASTCHandler.h index a637a214c..341782a52 100644 --- a/src/modules/image/magpie/ASTCHandler.h +++ b/src/modules/image/magpie/ASTCHandler.h @@ -42,8 +42,11 @@ public: virtual ~ASTCHandler() {} // Implements CompressedFormatHandler. - virtual bool canParse(const filesystem::FileData *data); - virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB); + bool canParse(const filesystem::FileData *data) override; + + StrongRef parse(filesystem::FileData *filedata, + std::vector> &images, + PixelFormat &format, bool &sRGB) override; }; // ASTCHandler diff --git a/src/modules/image/magpie/CompressedFormatHandler.h b/src/modules/image/magpie/CompressedFormatHandler.h index b0f158044..81328e5be 100644 --- a/src/modules/image/magpie/CompressedFormatHandler.h +++ b/src/modules/image/magpie/CompressedFormatHandler.h @@ -58,14 +58,14 @@ public: * @param[in] filedata The data to parse. * @param[out] images The list of sub-images generated. Byte data is a pointer * to the returned data. - * @param[out] dataSize The total size in bytes of the returned data. * @param[out] format The format of the Compressed Data. * @param[out] sRGB Whether the texture is sRGB-encoded. * * @return The single block of memory containing the parsed images. **/ - virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, - size_t &dataSize, PixelFormat &format, bool &sRGB) = 0; + virtual StrongRef parse(filesystem::FileData *filedata, + std::vector> &images, + PixelFormat &format, bool &sRGB) = 0; }; // CompressedFormatHandler diff --git a/src/modules/image/magpie/CompressedImageData.cpp b/src/modules/image/magpie/CompressedImageData.cpp index afc0770e9..4d3df3e46 100644 --- a/src/modules/image/magpie/CompressedImageData.cpp +++ b/src/modules/image/magpie/CompressedImageData.cpp @@ -43,42 +43,31 @@ CompressedImageData::CompressedImageData(std::list fo if (parser == nullptr) throw love::Exception("Could not parse compressed data: Unknown format."); - data = parser->parse(filedata, dataImages, dataSize, format, sRGB); + memory = parser->parse(filedata, dataImages, format, sRGB); - if (data == nullptr) + if (memory == nullptr) throw love::Exception("Could not parse compressed data."); if (format == PIXELFORMAT_UNKNOWN) - { - delete[] data; throw love::Exception("Could not parse compressed data: Unknown format."); - } - if (dataImages.size() == 0 || dataSize == 0) - { - delete[] data; + if (dataImages.size() == 0 || memory->size == 0) throw love::Exception("Could not parse compressed data: No valid data?"); - } } CompressedImageData::CompressedImageData(const CompressedImageData &c) { format = c.format; sRGB = c.sRGB; - dataSize = c.dataSize; - data = new uint8[dataSize]; - memcpy(data, c.data, dataSize); + memory.set(new Memory(c.memory->size), Acquire::NORETAIN); + memcpy(memory->data, c.memory->data, memory->size); - for (auto i : c.dataImages ) + for (const auto &i : c.dataImages) { - struct SubImage s; - s.width = i.width; - s.height = i.height; - s.size = i.size; - s.data = (uint8*)((intptr_t)data + (intptr_t)i.data - (intptr_t)c.data); - - dataImages.push_back(s); + Slice *slice = new Slice(i->getFormat(), i->getWidth(), i->getHeight(), memory, i->getOffset(), i->getSize()); + dataImages.push_back(slice); + slice->release(); } } @@ -89,7 +78,6 @@ CompressedImageData *CompressedImageData::clone() const CompressedImageData::~CompressedImageData() { - delete[] data; } } // magpie diff --git a/src/modules/image/magpie/ImageData.cpp b/src/modules/image/magpie/ImageData.cpp index 07959c099..1dc6bd0da 100644 --- a/src/modules/image/magpie/ImageData.cpp +++ b/src/modules/image/magpie/ImageData.cpp @@ -103,7 +103,7 @@ ImageData::~ImageData() handler->release(); } -ImageData *ImageData::clone() const +love::image::ImageData *ImageData::clone() const { return new ImageData(*this); } diff --git a/src/modules/image/magpie/ImageData.h b/src/modules/image/magpie/ImageData.h index a0275ffb2..0c053cd05 100644 --- a/src/modules/image/magpie/ImageData.h +++ b/src/modules/image/magpie/ImageData.h @@ -45,8 +45,8 @@ public: ImageData(const ImageData &c); virtual ~ImageData(); - virtual ImageData *clone() const; // Implements image::ImageData. + virtual love::image::ImageData *clone() const; virtual love::filesystem::FileData *encode(EncodedFormat encodedFormat, const char *filename); private: diff --git a/src/modules/image/magpie/KTXHandler.cpp b/src/modules/image/magpie/KTXHandler.cpp index 2b4657f9b..3d5cd7f34 100644 --- a/src/modules/image/magpie/KTXHandler.cpp +++ b/src/modules/image/magpie/KTXHandler.cpp @@ -297,7 +297,7 @@ bool KTXHandler::canParse(const filesystem::FileData *data) return true; } -uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB) +StrongRef KTXHandler::parse(filesystem::FileData *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParse(filedata)) throw love::Exception("Could not decode compressed data (not a KTX file?)"); @@ -353,15 +353,8 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector memory; + memory.set(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN); // Reset the file offset to the start of the file's image data. fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData; @@ -379,25 +372,23 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector> i, 1u); - mip.height = (int) std::max(header.pixelHeight >> i, 1u); - mip.size = mipsize; + int width = (int) std::max(header.pixelWidth >> i, 1u); + int height = (int) std::max(header.pixelHeight >> i, 1u); - memcpy(data + dataoffset, filebytes + fileoffset, mipsize); - mip.data = data + dataoffset; + memcpy(memory->data + dataoffset, filebytes + fileoffset, mipsize); + + auto slice = new CompressedImageData::Slice(cformat, width, height, memory, dataoffset, mipsize); + images.push_back(slice); + slice->release(); fileoffset += mipsizepadded; dataoffset += mipsizepadded; - - images.push_back(mip); } - dataSize = totalsize; format = cformat; sRGB = isSRGB; - return data; + return memory; } } // magpie diff --git a/src/modules/image/magpie/KTXHandler.h b/src/modules/image/magpie/KTXHandler.h index b6be0cb6e..965c7e3c0 100644 --- a/src/modules/image/magpie/KTXHandler.h +++ b/src/modules/image/magpie/KTXHandler.h @@ -41,8 +41,11 @@ public: virtual ~KTXHandler() {} // Implements CompressedFormatHandler. - virtual bool canParse(const filesystem::FileData *data); - virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB); + bool canParse(const filesystem::FileData *data) override; + + StrongRef parse(filesystem::FileData *filedata, + std::vector> &images, + PixelFormat &format, bool &sRGB) override; }; // KTXHandler diff --git a/src/modules/image/magpie/PKMHandler.cpp b/src/modules/image/magpie/PKMHandler.cpp index cae9e2fb1..81c799780 100644 --- a/src/modules/image/magpie/PKMHandler.cpp +++ b/src/modules/image/magpie/PKMHandler.cpp @@ -113,7 +113,7 @@ bool PKMHandler::canParse(const filesystem::FileData *data) return true; } -uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB) +StrongRef PKMHandler::parse(filesystem::FileData *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParse(filedata)) throw love::Exception("Could not decode compressed data (not a PKM file?)"); @@ -133,37 +133,24 @@ uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vectorgetSize() - sizeof(PKMHeader); - uint8 *data = nullptr; - try - { - data = new uint8[totalsize]; - } - catch (std::bad_alloc &) - { - throw love::Exception("Out of memory."); - } + StrongRef memory; + memory.set(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN); // PKM files only store a single mipmap level. - memcpy(data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize); - - CompressedImageData::SubImage mip; + memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize); // TODO: verify whether glCompressedTexImage works properly with the unpadded // width and height values (extended == padded.) - mip.width = header.widthBig; - mip.height = header.heightBig; + int width = header.widthBig; + int height = header.heightBig; - mip.size = totalsize; - mip.data = data; + images.emplace_back(new CompressedImageData::Slice(cformat, width, height, memory, 0, totalsize), Acquire::NORETAIN); - images.push_back(mip); - - dataSize = totalsize; format = cformat; sRGB = false; - return data; + return memory; } } // magpie diff --git a/src/modules/image/magpie/PKMHandler.h b/src/modules/image/magpie/PKMHandler.h index 2888cb40e..deb13ef1a 100644 --- a/src/modules/image/magpie/PKMHandler.h +++ b/src/modules/image/magpie/PKMHandler.h @@ -41,8 +41,11 @@ public: virtual ~PKMHandler() {} // Implements CompressedFormatHandler. - virtual bool canParse(const filesystem::FileData *data); - virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB); + bool canParse(const filesystem::FileData *data) override; + + StrongRef parse(filesystem::FileData *filedata, + std::vector> &images, + PixelFormat &format, bool &sRGB) override; }; // PKMHandler diff --git a/src/modules/image/magpie/PVRHandler.cpp b/src/modules/image/magpie/PVRHandler.cpp index 5e5ca0b35..387477fc1 100644 --- a/src/modules/image/magpie/PVRHandler.cpp +++ b/src/modules/image/magpie/PVRHandler.cpp @@ -474,7 +474,7 @@ bool PVRHandler::canParse(const filesystem::FileData *data) return false; } -uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB) +StrongRef PVRHandler::parse(filesystem::FileData *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParse(filedata)) throw love::Exception("Could not decode compressed data (not a PVR file?)"); @@ -525,14 +525,8 @@ uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vectorgetSize() < fileoffset + totalsize) throw love::Exception("Could not parse PVR file: invalid size calculation."); - try - { - data = new uint8[totalsize]; - } - catch (std::bad_alloc &) - { - throw love::Exception("Out of memory."); - } + StrongRef memory; + memory.set(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN); size_t curoffset = 0; const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset; @@ -544,24 +538,22 @@ uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector totalsize) break; // Just in case. - CompressedImageData::SubImage mip; - mip.width = std::max((int) header3.width >> i, 1); - mip.height = std::max((int) header3.height >> i, 1); - mip.size = mipsize; + int width = std::max((int) header3.width >> i, 1); + int height = std::max((int) header3.height >> i, 1); memcpy(data + curoffset, filebytes + curoffset, mipsize); - mip.data = data + curoffset; + + auto slice = new CompressedImageData::Slice(cformat, width, height, memory, curoffset, mipsize); + images.push_back(slice); + slice->release(); curoffset += mipsize; - - images.push_back(mip); } - dataSize = totalsize; format = cformat; sRGB = (header3.colorSpace == 1); - return data; + return memory; } } // magpie diff --git a/src/modules/image/magpie/PVRHandler.h b/src/modules/image/magpie/PVRHandler.h index 85964e3df..7cff3c24d 100644 --- a/src/modules/image/magpie/PVRHandler.h +++ b/src/modules/image/magpie/PVRHandler.h @@ -39,8 +39,11 @@ public: virtual ~PVRHandler() {} // Implements CompressedFormatHandler. - virtual bool canParse(const filesystem::FileData *data); - virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB); + bool canParse(const filesystem::FileData *data) override; + + StrongRef parse(filesystem::FileData *filedata, + std::vector> &images, + PixelFormat &format, bool &sRGB) override; }; // PVRHandler diff --git a/src/modules/image/magpie/ddsHandler.cpp b/src/modules/image/magpie/ddsHandler.cpp index 494641e57..400239e13 100644 --- a/src/modules/image/magpie/ddsHandler.cpp +++ b/src/modules/image/magpie/ddsHandler.cpp @@ -32,7 +32,7 @@ bool DDSHandler::canParse(const filesystem::FileData *data) return dds::isCompressedDDS(data->getData(), data->getSize()); } -uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB) +StrongRef DDSHandler::parse(filesystem::FileData *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!dds::isDDS(filedata->getData(), filedata->getSize())) throw love::Exception("Could not decode compressed data (not a DDS file?)"); @@ -40,65 +40,51 @@ uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector memory; + size_t dataSize = 0; + images.clear(); - try + // Attempt to parse the dds file. + dds::Parser parser(filedata->getData(), filedata->getSize()); + + texformat = convertFormat(parser.getFormat(), isSRGB); + + if (texformat == PIXELFORMAT_UNKNOWN) + throw love::Exception("Could not parse compressed data: Unsupported format."); + + if (parser.getMipmapCount() == 0) + throw love::Exception("Could not parse compressed data: No readable texture data."); + + // Calculate the size of the block of memory we're returning. + for (size_t i = 0; i < parser.getMipmapCount(); i++) { - // Attempt to parse the dds file. - dds::Parser parser(filedata->getData(), filedata->getSize()); - - texformat = convertFormat(parser.getFormat(), isSRGB); - - if (texformat == PIXELFORMAT_UNKNOWN) - throw love::Exception("Could not parse compressed data: Unsupported format."); - - if (parser.getMipmapCount() == 0) - throw love::Exception("Could not parse compressed data: No readable texture data."); - - // Calculate the size of the block of memory we're returning. - for (size_t i = 0; i < parser.getMipmapCount(); i++) - { - const dds::Image *img = parser.getImageData(i); - dataSize += img->dataSize; - } - - data = new uint8[dataSize]; - - size_t dataOffset = 0; - - // Copy the parsed mipmap levels from the FileData to our CompressedImageData. - for (size_t i = 0; i < parser.getMipmapCount(); i++) - { - // Fetch the data for this mipmap level. - const dds::Image *img = parser.getImageData(i); - - CompressedImageData::SubImage mip; - - mip.width = img->width; - mip.height = img->height; - mip.size = img->dataSize; - - // Copy the mipmap image from the FileData to our block of memory. - memcpy(data + dataOffset, img->data, mip.size); - mip.data = data + dataOffset; - - dataOffset += mip.size; - - images.push_back(mip); - } + const dds::Image *img = parser.getImageData(i); + dataSize += img->dataSize; } - catch (std::exception &e) + + memory.set(new CompressedImageData::Memory(dataSize), Acquire::NORETAIN); + + size_t dataOffset = 0; + + // Copy the parsed mipmap levels from the FileData to our CompressedImageData. + for (size_t i = 0; i < parser.getMipmapCount(); i++) { - delete[] data; - images.clear(); - throw love::Exception("%s", e.what()); + // Fetch the data for this mipmap level. + const dds::Image *img = parser.getImageData(i); + + // Copy the mipmap image from the FileData to our block of memory. + memcpy(memory->data + dataOffset, img->data, img->dataSize); + + auto slice = new CompressedImageData::Slice(texformat, img->width, img->height, memory, dataOffset, img->dataSize); + images.emplace_back(slice, Acquire::NORETAIN); + + dataOffset += img->dataSize; } format = texformat; sRGB = isSRGB; - return data; + return memory; } PixelFormat DDSHandler::convertFormat(dds::Format ddsformat, bool &sRGB) diff --git a/src/modules/image/magpie/ddsHandler.h b/src/modules/image/magpie/ddsHandler.h index c442e95c6..fde057729 100644 --- a/src/modules/image/magpie/ddsHandler.h +++ b/src/modules/image/magpie/ddsHandler.h @@ -47,8 +47,11 @@ public: virtual ~DDSHandler() {} // Implements CompressedFormatHandler. - virtual bool canParse(const filesystem::FileData *data); - virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, PixelFormat &format, bool &sRGB); + bool canParse(const filesystem::FileData *data) override; + + StrongRef parse(filesystem::FileData *filedata, + std::vector> &images, + PixelFormat &format, bool &sRGB) override; private: diff --git a/src/modules/image/wrap_Image.cpp b/src/modules/image/wrap_Image.cpp index 55579ae54..ace0db780 100644 --- a/src/modules/image/wrap_Image.cpp +++ b/src/modules/image/wrap_Image.cpp @@ -122,12 +122,23 @@ int w_isCompressed(lua_State *L) return 1; } +int w_newCubeFaces(lua_State *L) +{ + ImageData *id = luax_checkimagedata(L, 1); + std::vector> faces; + luax_catchexcept(L, [&](){ faces = instance()->newCubeFaces(id); }); + for (auto face : faces) + luax_pushtype(L, face); + return (int) faces.size(); +} + // List of functions to wrap. static const luaL_Reg functions[] = { { "newImageData", w_newImageData }, { "newCompressedData", w_newCompressedData }, { "isCompressed", w_isCompressed }, + { "newCubeFaces", w_newCubeFaces }, { 0, 0 } };