diff --git a/CMakeLists.txt b/CMakeLists.txt index 72953c5e4..726e778db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,8 +518,6 @@ source_group("modules\\font\\freetype" FILES ${LOVE_SRC_MODULE_FONT_FREETYPE}) set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/Buffer.cpp src/modules/graphics/Buffer.h - src/modules/graphics/Canvas.cpp - src/modules/graphics/Canvas.h src/modules/graphics/Deprecations.cpp src/modules/graphics/Deprecations.h src/modules/graphics/Drawable.cpp @@ -528,8 +526,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/Font.h src/modules/graphics/Graphics.cpp src/modules/graphics/Graphics.h - src/modules/graphics/Image.cpp - src/modules/graphics/Image.h src/modules/graphics/Mesh.cpp src/modules/graphics/Mesh.h src/modules/graphics/ParticleSystem.cpp @@ -559,14 +555,10 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/Video.h src/modules/graphics/Volatile.cpp src/modules/graphics/Volatile.h - src/modules/graphics/wrap_Canvas.cpp - src/modules/graphics/wrap_Canvas.h src/modules/graphics/wrap_Font.cpp src/modules/graphics/wrap_Font.h src/modules/graphics/wrap_Graphics.cpp src/modules/graphics/wrap_Graphics.h - src/modules/graphics/wrap_Image.cpp - src/modules/graphics/wrap_Image.h src/modules/graphics/wrap_Mesh.cpp src/modules/graphics/wrap_Mesh.h src/modules/graphics/wrap_ParticleSystem.cpp @@ -588,14 +580,10 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/Buffer.cpp src/modules/graphics/opengl/Buffer.h - src/modules/graphics/opengl/Canvas.cpp - src/modules/graphics/opengl/Canvas.h src/modules/graphics/opengl/FenceSync.cpp src/modules/graphics/opengl/FenceSync.h src/modules/graphics/opengl/Graphics.cpp src/modules/graphics/opengl/Graphics.h - src/modules/graphics/opengl/Image.cpp - src/modules/graphics/opengl/Image.h src/modules/graphics/opengl/OpenGL.cpp src/modules/graphics/opengl/OpenGL.h src/modules/graphics/opengl/Shader.cpp @@ -604,6 +592,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/ShaderStage.h src/modules/graphics/opengl/StreamBuffer.cpp src/modules/graphics/opengl/StreamBuffer.h + src/modules/graphics/opengl/Texture.cpp + src/modules/graphics/opengl/Texture.h ) set(LOVE_SRC_MODULE_GRAPHICS diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index b9dd0bd6f..1ad92302b 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -403,15 +403,12 @@ FA0B7D301A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */; }; FA0B7D311A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */; }; FA0B7D321A95902C000E1D17 /* Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B8B1A95902C000E1D17 /* Graphics.h */; }; - 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 */; }; 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 */; }; - FA0B7D3C1A95902C000E1D17 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B931A95902C000E1D17 /* Image.cpp */; }; - FA0B7D3D1A95902C000E1D17 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B931A95902C000E1D17 /* Image.cpp */; }; - FA0B7D3E1A95902C000E1D17 /* Image.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B941A95902C000E1D17 /* Image.h */; }; + FA0B7D3C1A95902C000E1D17 /* Texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B931A95902C000E1D17 /* Texture.cpp */; }; + FA0B7D3D1A95902C000E1D17 /* Texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B931A95902C000E1D17 /* Texture.cpp */; }; + FA0B7D3E1A95902C000E1D17 /* Texture.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B941A95902C000E1D17 /* Texture.h */; }; FA0B7D421A95902C000E1D17 /* OpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B971A95902C000E1D17 /* OpenGL.cpp */; }; FA0B7D431A95902C000E1D17 /* OpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B971A95902C000E1D17 /* OpenGL.cpp */; }; FA0B7D441A95902C000E1D17 /* OpenGL.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B981A95902C000E1D17 /* OpenGL.h */; }; @@ -856,12 +853,6 @@ FA1BA0A21E16D97500AA2803 /* wrap_Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */; }; FA1BA0A31E16D97500AA2803 /* wrap_Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */; }; FA1BA0A41E16D97500AA2803 /* wrap_Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA0A11E16D97500AA2803 /* wrap_Font.h */; }; - FA1BA0A71E16F20600AA2803 /* Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0A51E16F20600AA2803 /* Canvas.cpp */; }; - FA1BA0A81E16F20600AA2803 /* Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0A51E16F20600AA2803 /* Canvas.cpp */; }; - FA1BA0A91E16F20600AA2803 /* Canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA0A61E16F20600AA2803 /* Canvas.h */; }; - FA1BA0AC1E16F9EE00AA2803 /* wrap_Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0AA1E16F9EE00AA2803 /* wrap_Canvas.cpp */; }; - FA1BA0AD1E16F9EE00AA2803 /* wrap_Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0AA1E16F9EE00AA2803 /* wrap_Canvas.cpp */; }; - FA1BA0AE1E16F9EE00AA2803 /* wrap_Canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA0AB1E16F9EE00AA2803 /* wrap_Canvas.h */; }; FA1BA0B11E16FD0800AA2803 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0AF1E16FD0800AA2803 /* Shader.cpp */; }; FA1BA0B21E16FD0800AA2803 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0AF1E16FD0800AA2803 /* Shader.cpp */; }; FA1BA0B31E16FD0800AA2803 /* Shader.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA0B01E16FD0800AA2803 /* Shader.h */; }; @@ -1129,12 +1120,6 @@ FADF540E1E3D7CDD00012CC0 /* wrap_Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF540A1E3D7CDD00012CC0 /* wrap_Video.cpp */; }; FADF540F1E3D7CDD00012CC0 /* wrap_Video.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF540B1E3D7CDD00012CC0 /* wrap_Video.h */; }; FADF54101E3D7CDD00012CC0 /* wrap_Video.lua in Resources */ = {isa = PBXBuildFile; fileRef = FADF540C1E3D7CDD00012CC0 /* wrap_Video.lua */; }; - FADF54161E3DA08E00012CC0 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF54141E3DA08E00012CC0 /* Image.cpp */; }; - FADF54171E3DA08E00012CC0 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF54141E3DA08E00012CC0 /* Image.cpp */; }; - FADF54181E3DA08E00012CC0 /* Image.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF54151E3DA08E00012CC0 /* Image.h */; }; - FADF541B1E3DA46C00012CC0 /* wrap_Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF54191E3DA46C00012CC0 /* wrap_Image.cpp */; }; - FADF541C1E3DA46C00012CC0 /* wrap_Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF54191E3DA46C00012CC0 /* wrap_Image.cpp */; }; - FADF541D1E3DA46C00012CC0 /* wrap_Image.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF541A1E3DA46C00012CC0 /* wrap_Image.h */; }; FADF54201E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */; }; FADF54211E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */; }; FADF54221E3DA52C00012CC0 /* wrap_ParticleSystem.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF541F1E3DA52C00012CC0 /* wrap_ParticleSystem.h */; }; @@ -1617,12 +1602,10 @@ FA0B7B891A95902C000E1D17 /* Drawable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Drawable.h; sourceTree = ""; }; FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Graphics.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; 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; lineEnding = 0; path = Canvas.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; - FA0B7B8E1A95902C000E1D17 /* Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Canvas.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 = ""; }; - FA0B7B941A95902C000E1D17 /* Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Image.h; sourceTree = ""; }; + FA0B7B931A95902C000E1D17 /* Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Texture.cpp; sourceTree = ""; }; + FA0B7B941A95902C000E1D17 /* Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Texture.h; sourceTree = ""; }; FA0B7B971A95902C000E1D17 /* OpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OpenGL.cpp; sourceTree = ""; }; FA0B7B981A95902C000E1D17 /* OpenGL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenGL.h; sourceTree = ""; }; FA0B7B9B1A95902C000E1D17 /* Polyline.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Polyline.cpp; sourceTree = ""; }; @@ -1925,10 +1908,6 @@ FA1BA09C1E16CFCE00AA2803 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = ""; }; FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = ""; }; FA1BA0A11E16D97500AA2803 /* wrap_Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Font.h; sourceTree = ""; }; - FA1BA0A51E16F20600AA2803 /* Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Canvas.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; - FA1BA0A61E16F20600AA2803 /* Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Canvas.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - FA1BA0AA1E16F9EE00AA2803 /* wrap_Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = wrap_Canvas.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; - FA1BA0AB1E16F9EE00AA2803 /* wrap_Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Canvas.h; sourceTree = ""; }; FA1BA0AF1E16FD0800AA2803 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; FA1BA0B01E16FD0800AA2803 /* Shader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Shader.h; sourceTree = ""; }; FA1BA0B51E17043400AA2803 /* wrap_Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Shader.cpp; sourceTree = ""; }; @@ -2129,10 +2108,6 @@ FADF540A1E3D7CDD00012CC0 /* wrap_Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Video.cpp; sourceTree = ""; }; FADF540B1E3D7CDD00012CC0 /* wrap_Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Video.h; sourceTree = ""; }; FADF540C1E3D7CDD00012CC0 /* wrap_Video.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Video.lua; sourceTree = ""; }; - FADF54141E3DA08E00012CC0 /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = ""; }; - FADF54151E3DA08E00012CC0 /* Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Image.h; sourceTree = ""; }; - FADF54191E3DA46C00012CC0 /* wrap_Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Image.cpp; sourceTree = ""; }; - FADF541A1E3DA46C00012CC0 /* wrap_Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Image.h; sourceTree = ""; }; FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ParticleSystem.cpp; sourceTree = ""; }; FADF541F1E3DA52C00012CC0 /* wrap_ParticleSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ParticleSystem.h; sourceTree = ""; }; FADF54231E3DA5BA00012CC0 /* Mesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Mesh.cpp; sourceTree = ""; }; @@ -2959,8 +2934,6 @@ children = ( FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */, FADF53F71E3C7ACD00012CC0 /* Buffer.h */, - FA1BA0A51E16F20600AA2803 /* Canvas.cpp */, - FA1BA0A61E16F20600AA2803 /* Canvas.h */, FA9D53AA1F5307E900125C6B /* Deprecations.cpp */, FA9D53AB1F5307E900125C6B /* Deprecations.h */, FA9D8DDC1DEF842A002CD881 /* Drawable.cpp */, @@ -2969,8 +2942,6 @@ FA1BA09C1E16CFCE00AA2803 /* Font.h */, FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */, FA0B7B8B1A95902C000E1D17 /* Graphics.h */, - FADF54141E3DA08E00012CC0 /* Image.cpp */, - FADF54151E3DA08E00012CC0 /* Image.h */, FADF54231E3DA5BA00012CC0 /* Mesh.cpp */, FADF54241E3DA5BA00012CC0 /* Mesh.h */, FA18CECC23DBC6E000263725 /* metal */, @@ -3002,16 +2973,12 @@ FADF54061E3D78F700012CC0 /* Video.h */, FA0B7BC01A95902C000E1D17 /* Volatile.cpp */, FA0B7BC11A95902C000E1D17 /* Volatile.h */, - FA1BA0AA1E16F9EE00AA2803 /* wrap_Canvas.cpp */, - FA1BA0AB1E16F9EE00AA2803 /* wrap_Canvas.h */, FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */, FA1BA0A11E16D97500AA2803 /* wrap_Font.h */, FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */, FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */, FADF54371E3DAFBA00012CC0 /* wrap_Graphics.lua */, FA665DC321C34C900074BBD6 /* wrap_GraphicsShader.lua */, - FADF54191E3DA46C00012CC0 /* wrap_Image.cpp */, - FADF541A1E3DA46C00012CC0 /* wrap_Image.h */, FADF54281E3DAADA00012CC0 /* wrap_Mesh.cpp */, FADF54291E3DAADA00012CC0 /* wrap_Mesh.h */, FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */, @@ -3038,14 +3005,10 @@ children = ( FA0B7BA41A95902C000E1D17 /* Buffer.cpp */, FA0B7BA51A95902C000E1D17 /* Buffer.h */, - FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */, - FA0B7B8E1A95902C000E1D17 /* Canvas.h */, FA28EBD31E352DB5003446F4 /* FenceSync.cpp */, FA28EBD41E352DB5003446F4 /* FenceSync.h */, FA0B7B911A95902C000E1D17 /* Graphics.cpp */, FA0B7B921A95902C000E1D17 /* Graphics.h */, - FA0B7B931A95902C000E1D17 /* Image.cpp */, - FA0B7B941A95902C000E1D17 /* Image.h */, FA0B7B971A95902C000E1D17 /* OpenGL.cpp */, FA0B7B981A95902C000E1D17 /* OpenGL.h */, FA0B7B9D1A95902C000E1D17 /* Shader.cpp */, @@ -3054,6 +3017,8 @@ FA3C5E461F8D80CA0003C579 /* ShaderStage.h */, FA7634481E28722A0066EF9E /* StreamBuffer.cpp */, FA7634491E28722A0066EF9E /* StreamBuffer.h */, + FA0B7B931A95902C000E1D17 /* Texture.cpp */, + FA0B7B941A95902C000E1D17 /* Texture.h */, ); path = opengl; sourceTree = ""; @@ -4054,10 +4019,8 @@ FA0B7CFC1A95902C000E1D17 /* Filesystem.h in Headers */, FA0B7AD81A958EA3000E1D17 /* lua-enet.h in Headers */, FA0B7A3A1A958EA3000E1D17 /* b2DynamicTree.h in Headers */, - FA0B7D351A95902C000E1D17 /* Canvas.h in Headers */, - FA18CEEE23DC9B3E00263725 /* Image.h in Headers */, FA0B7EBA1A95902C000E1D17 /* Channel.h in Headers */, - FA0B7D3E1A95902C000E1D17 /* Image.h in Headers */, + FA0B7D3E1A95902C000E1D17 /* Texture.h in Headers */, FA0B7ECA1A95902C000E1D17 /* threads.h in Headers */, FADF54361E3DAE6E00012CC0 /* wrap_SpriteBatch.h in Headers */, FA0B7DB01A95902C000E1D17 /* wrap_CompressedImageData.h in Headers */, @@ -4150,7 +4113,6 @@ FAF140A51E20934C00F898D2 /* Scan.h in Headers */, FA0B7CE11A95902C000E1D17 /* Source.h in Headers */, FA24348621D401CB00B8918A /* attribute.h in Headers */, - FA1BA0AE1E16F9EE00AA2803 /* wrap_Canvas.h in Headers */, FAF140901E20934C00F898D2 /* PpContext.h in Headers */, FA0B7E621A95902C000E1D17 /* wrap_Physics.h in Headers */, FA0B7DF01A95902C000E1D17 /* Mouse.h in Headers */, @@ -4190,8 +4152,6 @@ FAB17BE81ABFAA9000F9BA27 /* lz4.h in Headers */, FA0B7E6B1A95902C000E1D17 /* wrap_PulleyJoint.h in Headers */, FA0B7E051A95902C000E1D17 /* Contact.h in Headers */, - FADF541D1E3DA46C00012CC0 /* wrap_Image.h in Headers */, - FA1BA0A91E16F20600AA2803 /* Canvas.h in Headers */, FA0B7A691A958EA3000E1D17 /* b2Island.h in Headers */, FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */, FA0B7E0E1A95902C000E1D17 /* Fixture.h in Headers */, @@ -4280,7 +4240,6 @@ FA0B7A601A958EA3000E1D17 /* b2Body.h in Headers */, FA0B7EAB1A95902C000E1D17 /* wrap_Sound.h in Headers */, FA0B7B2C1A958EA3000E1D17 /* checked.h in Headers */, - FADF54181E3DA08E00012CC0 /* Image.h in Headers */, FA0B7D2A1A95902C000E1D17 /* wrap_GlyphData.h in Headers */, FACA02F11F5E396B0084B28F /* DataModule.h in Headers */, FA0B7E741A95902C000E1D17 /* wrap_Shape.h in Headers */, @@ -4584,7 +4543,6 @@ FA0B7ECC1A95902C000E1D17 /* wrap_Channel.cpp in Sources */, FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */, FA0B7A5F1A958EA3000E1D17 /* b2Body.cpp in Sources */, - FADF541C1E3DA46C00012CC0 /* wrap_Image.cpp in Sources */, FACA02FA1F5E397B0084B28F /* DataModule.cpp in Sources */, FA0B7E641A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */, FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */, @@ -4654,7 +4612,7 @@ FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */, FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */, FA0B7CFB1A95902C000E1D17 /* Filesystem.cpp in Sources */, - FA0B7D3D1A95902C000E1D17 /* Image.cpp in Sources */, + FA0B7D3D1A95902C000E1D17 /* Texture.cpp in Sources */, FA0B7B351A958EA3000E1D17 /* wuff_convert.c in Sources */, FAF140941E20934C00F898D2 /* PpScanner.cpp in Sources */, FA9D53AD1F5307E900125C6B /* Deprecations.cpp in Sources */, @@ -4822,7 +4780,6 @@ FA0B79411A958E3B000E1D17 /* utf8.cpp in Sources */, FAE64A862071363100BC7981 /* physfs_archiver_qpak.c in Sources */, FA0B7ADF1A958EA3000E1D17 /* lodepng.cpp in Sources */, - FA0B7D341A95902C000E1D17 /* Canvas.cpp in Sources */, FAF140761E20934C00F898D2 /* IntermTraverse.cpp in Sources */, FA0B7E8C1A95902C000E1D17 /* FLACDecoder.cpp in Sources */, FA0B7A421A958EA3000E1D17 /* b2CircleShape.cpp in Sources */, @@ -4890,7 +4847,6 @@ FA0B7DBF1A95902C000E1D17 /* JoystickModule.cpp in Sources */, FAB2D5AB1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */, FA0B7A9F1A958EA3000E1D17 /* b2PrismaticJoint.cpp in Sources */, - FA1BA0AD1E16F9EE00AA2803 /* wrap_Canvas.cpp in Sources */, FAF6C9FB23C2DE2900D7B5BC /* disassemble.cpp in Sources */, FA18CEDD23DBC6E000263725 /* Metal.mm in Sources */, FAE64A822071363100BC7981 /* physfs_archiver_grp.c in Sources */, @@ -4914,10 +4870,8 @@ FA0B7EAD1A95902C000E1D17 /* wrap_SoundData.cpp in Sources */, FA0B7E2E1A95902C000E1D17 /* RopeJoint.cpp in Sources */, FA0B7CE01A95902C000E1D17 /* Source.cpp in Sources */, - FADF54171E3DA08E00012CC0 /* Image.cpp in Sources */, FA18CED923DBC6E000263725 /* StreamBuffer.mm in Sources */, FA0B7ECF1A95902C000E1D17 /* wrap_LuaThread.cpp in Sources */, - FA1BA0A81E16F20600AA2803 /* Canvas.cpp in Sources */, FA0B7AA51A958EA3000E1D17 /* b2RevoluteJoint.cpp in Sources */, FA0B7EA11A95902C000E1D17 /* Sound.cpp in Sources */, FA0B7DE61A95902C000E1D17 /* Cursor.cpp in Sources */, @@ -5005,7 +4959,6 @@ FA0B7E6C1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */, FA0B7A5E1A958EA3000E1D17 /* b2Body.cpp in Sources */, FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */, - FADF541B1E3DA46C00012CC0 /* wrap_Image.cpp in Sources */, FAC7CD7B1FE35E95006A60C7 /* physfs_platform_unix.c in Sources */, FACA02F01F5E396B0084B28F /* DataModule.cpp in Sources */, FA0B7E721A95902C000E1D17 /* wrap_Shape.cpp in Sources */, @@ -5074,7 +5027,7 @@ FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */, FA1BA0A21E16D97500AA2803 /* wrap_Font.cpp in Sources */, FAC7CD781FE35E95006A60C7 /* physfs_platform_qnx.c in Sources */, - FA0B7D3C1A95902C000E1D17 /* Image.cpp in Sources */, + FA0B7D3C1A95902C000E1D17 /* Texture.cpp in Sources */, FA0B7A8C1A958EA3000E1D17 /* b2DistanceJoint.cpp in Sources */, FADF53FD1E3D74F200012CC0 /* Text.cpp in Sources */, FA6A2B741F60B6710074C308 /* ByteData.cpp in Sources */, @@ -5245,7 +5198,6 @@ FA0B7AD41A958EA3000E1D17 /* unix.c in Sources */, FA0B7A771A958EA3000E1D17 /* b2CircleContact.cpp in Sources */, FADF543B1E3DAFF700012CC0 /* wrap_Graphics.cpp in Sources */, - FA0B7D331A95902C000E1D17 /* Canvas.cpp in Sources */, FA0B7E941A95902C000E1D17 /* Mpg123Decoder.cpp in Sources */, FA0B7E8B1A95902C000E1D17 /* FLACDecoder.cpp in Sources */, FA0B7B3A1A958EA3000E1D17 /* wuff_memory.c in Sources */, @@ -5314,7 +5266,6 @@ FA0B7E5A1A95902C000E1D17 /* wrap_MotorJoint.cpp in Sources */, FA0B7AA71A958EA3000E1D17 /* b2RopeJoint.cpp in Sources */, FA0B7DD61A95902C000E1D17 /* MathModule.cpp in Sources */, - FA1BA0AC1E16F9EE00AA2803 /* wrap_Canvas.cpp in Sources */, FAC7CD8A1FE35E95006A60C7 /* physfs_byteorder.c in Sources */, FA0B7D0F1A95902C000E1D17 /* BMFontRasterizer.cpp in Sources */, FA0B7E9A1A95902C000E1D17 /* VorbisDecoder.cpp in Sources */, @@ -5335,9 +5286,7 @@ FA4F2BE31DE6650600CA37D7 /* Transform.cpp in Sources */, FA0B7EA01A95902C000E1D17 /* Sound.cpp in Sources */, FA0B7DE51A95902C000E1D17 /* Cursor.cpp in Sources */, - FADF54161E3DA08E00012CC0 /* Image.cpp in Sources */, FA0B7EDB1A95902D000E1D17 /* Touch.cpp in Sources */, - FA1BA0A71E16F20600AA2803 /* Canvas.cpp in Sources */, FA0B7CE81A95902C000E1D17 /* Event.cpp in Sources */, FA0B7ACF1A958EA3000E1D17 /* peer.c in Sources */, FA0B7ADE1A958EA3000E1D17 /* lodepng.cpp in Sources */, diff --git a/src/common/Optional.h b/src/common/Optional.h index 0bb762643..c0cfbb8a6 100644 --- a/src/common/Optional.h +++ b/src/common/Optional.h @@ -45,6 +45,16 @@ struct Optional value = val; hasValue = true; } + + T get(T defaultVal) + { + return hasValue ? value : defaultVal; + } + + void clear() + { + hasValue = false; + } }; typedef Optional OptionalBool; diff --git a/src/common/pixelformat.cpp b/src/common/pixelformat.cpp index 56defbc61..53426ceb3 100644 --- a/src/common/pixelformat.cpp +++ b/src/common/pixelformat.cpp @@ -24,6 +24,87 @@ namespace love { +static PixelFormatInfo formatInfo[] = +{ + // components, blockW, blockH, blockSize, color, depth, stencil, compressed + { 0, 1, 1, 0, false, false, false, false }, // PIXELFORMAT_UNKNOWN + + { 0, 1, 1, 0, true, false, false, false }, // PIXELFORMAT_NORMAL + { 0, 1, 1, 0, true, false, false, false }, // PIXELFORMAT_HDR + + { 1, 1, 1, 1, true, false, false, false }, // PIXELFORMAT_R8_UNORM + { 1, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_R16_UNORM + { 1, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_R16_FLOAT + { 1, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_R32_FLOAT + + { 2, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_RG8_UNORM + { 2, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_LA8_UNORM + { 2, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_RG16_UNORM + { 2, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_RG16_FLOAT + { 2, 1, 1, 8, true, false, false, false }, // PIXELFORMAT_RG32_FLOAT + + { 4, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_RGBA8_UNORM + { 4, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_sRGBA8_UNORM + { 4, 1, 1, 8, true, false, false, false }, // PIXELFORMAT_RGBA16_UNORM + { 4, 1, 1, 8, true, false, false, false }, // PIXELFORMAT_RGBA16_FLOAT + { 4, 1, 1, 16, true, false, false, false }, // PIXELFORMAT_RGBA32_FLOAT + + { 4, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_RGBA4_UNORM + { 4, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_RGB5A1_UNORM + { 3, 1, 1, 2, true, false, false, false }, // PIXELFORMAT_RGB565_UNORM + { 4, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_RGB10A2_UNORM + { 3, 1, 1, 4, true, false, false, false }, // PIXELFORMAT_RG11B10_FLOAT + + { 1, 1, 1, 1, false, false, true , false }, // PIXELFORMAT_STENCIL8 + { 1, 1, 1, 2, false, true, false, false }, // PIXELFORMAT_DEPTH16_UNORM + { 1, 1, 1, 3, false, true, false, false }, // PIXELFORMAT_DEPTH24_UNORM + { 1, 1, 1, 4, false, true, false, false }, // PIXELFORMAT_DEPTH32_FLOAT + { 2, 1, 1, 4, false, true, true , false }, // PIXELFORMAT_DEPTH24_UNORM_STENCIL8 + { 2, 1, 1, 5, false, true, true , false }, // PIXELFORMAT_DEPTH32_FLOAT_STENCIL8 + + { 3, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_DXT1_UNORM + { 4, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_DXT3_UNORM + { 4, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_DXT5_UNORM + { 1, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_BC4_UNORM + { 1, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_BC4_SNORM + { 2, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_BC5_UNORM + { 2, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_BC5_SNORM + { 3, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_BC6H_UFLOAT + { 3, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_BC6H_FLOAT + { 4, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_BC7_UNORM + + { 3, 16, 8, 32, true, false, false, true }, // PIXELFORMAT_PVR1_RGB2_UNORM + { 3, 8, 8, 32, true, false, false, true }, // PIXELFORMAT_PVR1_RGB4_UNORM + { 4, 16, 8, 32, true, false, false, true }, // PIXELFORMAT_PVR1_RGBA2_UNORM + { 4, 8, 8, 32, true, false, false, true }, // PIXELFORMAT_PVR1_RGBA4_UNORM + + { 3, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_ETC1_UNORM + { 3, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_ETC2_RGB_UNORM + { 4, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_ETC2_RGBA_UNORM + { 4, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_ETC2_RGBA1_UNORM + { 1, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_EAC_R_UNORM + { 1, 4, 4, 8, true, false, false, true }, // PIXELFORMAT_EAC_R_SNORM + { 2, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_EAC_RG_UNORM + { 2, 4, 4, 16, true, false, false, true }, // PIXELFORMAT_EAC_RG_SNORM + + { 4, 4, 4, 1, true, false, false, true }, // PIXELFORMAT_ASTC_4x4 + { 4, 5, 4, 1, true, false, false, true }, // PIXELFORMAT_ASTC_5x4 + { 4, 5, 5, 1, true, false, false, true }, // PIXELFORMAT_ASTC_5x5 + { 4, 6, 5, 1, true, false, false, true }, // PIXELFORMAT_ASTC_6x5 + { 4, 6, 6, 1, true, false, false, true }, // PIXELFORMAT_ASTC_6x6 + { 4, 8, 5, 1, true, false, false, true }, // PIXELFORMAT_ASTC_8x5 + { 4, 8, 6, 1, true, false, false, true }, // PIXELFORMAT_ASTC_8x6 + { 4, 8, 8, 1, true, false, false, true }, // PIXELFORMAT_ASTC_8x8 + { 4, 8, 5, 1, true, false, false, true }, // PIXELFORMAT_ASTC_10x5 + { 4, 10, 6, 1, true, false, false, true }, // PIXELFORMAT_ASTC_10x6 + { 4, 10, 8, 1, true, false, false, true }, // PIXELFORMAT_ASTC_10x8 + { 4, 10, 10, 1, true, false, false, true }, // PIXELFORMAT_ASTC_10x10 + { 4, 12, 10, 1, true, false, false, true }, // PIXELFORMAT_ASTC_12x10 + { 4, 12, 12, 1, true, false, false, true }, // PIXELFORMAT_ASTC_12x12 +}; + +static_assert(sizeof(formatInfo) / sizeof(PixelFormatInfo) == PIXELFORMAT_MAX_ENUM, "Update the formatInfo array when adding or removing a PixelFormat"); + static StringMap::Entry formatEntries[] = { { "unknown", PIXELFORMAT_UNKNOWN }, @@ -113,68 +194,78 @@ bool getConstant(PixelFormat in, const char *&out) return formats.find(in, out); } +const PixelFormatInfo &getPixelFormatInfo(PixelFormat format) +{ + return formatInfo[format]; +} + bool isPixelFormatCompressed(PixelFormat format) { - // I'm lazy - int iformat = (int) format; - return iformat >= (int) PIXELFORMAT_DXT1_UNORM && iformat < (int) PIXELFORMAT_MAX_ENUM; + return formatInfo[format].compressed; } bool isPixelFormatDepthStencil(PixelFormat format) { - int iformat = (int) format; - return iformat >= (int) PIXELFORMAT_STENCIL8 && iformat <= (int) PIXELFORMAT_DEPTH32_FLOAT_STENCIL8; + const PixelFormatInfo &info = formatInfo[format]; + return info.depth || info.stencil; } bool isPixelFormatDepth(PixelFormat format) { - int iformat = (int) format; - return iformat >= (int) PIXELFORMAT_DEPTH16_UNORM && iformat <= (int) PIXELFORMAT_DEPTH32_FLOAT_STENCIL8; + return formatInfo[format].depth; } bool isPixelFormatStencil(PixelFormat format) { - return format == PIXELFORMAT_STENCIL8 || format == PIXELFORMAT_DEPTH24_UNORM_STENCIL8 || format == PIXELFORMAT_DEPTH32_FLOAT_STENCIL8; + return formatInfo[format].stencil; } -size_t getPixelFormatSize(PixelFormat format) +PixelFormat getSRGBPixelFormat(PixelFormat format) { - switch (format) - { - case PIXELFORMAT_R8_UNORM: - case PIXELFORMAT_STENCIL8: - return 1; - case PIXELFORMAT_RG8_UNORM: - case PIXELFORMAT_R16_UNORM: - case PIXELFORMAT_R16_FLOAT: - case PIXELFORMAT_LA8_UNORM: - case PIXELFORMAT_RGBA4_UNORM: - case PIXELFORMAT_RGB5A1_UNORM: - case PIXELFORMAT_RGB565_UNORM: - case PIXELFORMAT_DEPTH16_UNORM: - return 2; - case PIXELFORMAT_RGBA8_UNORM: - case PIXELFORMAT_sRGBA8_UNORM: - case PIXELFORMAT_RG16_UNORM: - case PIXELFORMAT_RG16_FLOAT: - case PIXELFORMAT_R32_FLOAT: - case PIXELFORMAT_RGB10A2_UNORM: - case PIXELFORMAT_RG11B10_FLOAT: - case PIXELFORMAT_DEPTH24_UNORM: - case PIXELFORMAT_DEPTH32_FLOAT: - case PIXELFORMAT_DEPTH24_UNORM_STENCIL8: - return 4; - case PIXELFORMAT_RGBA16_UNORM: - case PIXELFORMAT_RGBA16_FLOAT: - case PIXELFORMAT_RG32_FLOAT: - case PIXELFORMAT_DEPTH32_FLOAT_STENCIL8: - return 8; - case PIXELFORMAT_RGBA32_FLOAT: - return 16; - default: - // TODO: compressed formats - return 0; - } + if (format == PIXELFORMAT_RGBA8_UNORM) + return PIXELFORMAT_sRGBA8_UNORM; + return format; +} + +PixelFormat getLinearPixelFormat(PixelFormat format) +{ + if (format == PIXELFORMAT_sRGBA8_UNORM) + return PIXELFORMAT_RGBA8_UNORM; + return format; +} + +size_t getPixelFormatBlockSize(PixelFormat format) +{ + return formatInfo[format].blockSize; +} + +size_t getPixelFormatUncompressedRowSize(PixelFormat format, int width) +{ + const PixelFormatInfo &info = formatInfo[format]; + if (info.compressed) return 0; + return info.blockSize * width / info.blockWidth; +} + +size_t getPixelFormatCompressedBlockRowSize(PixelFormat format, int width) +{ + const PixelFormatInfo &info = formatInfo[format]; + if (!info.compressed) return 0; + return info.blockSize * ((width + info.blockWidth - 1) / info.blockWidth); +} + +size_t getPixelFormatCompressedBlockRowCount(PixelFormat format, int height) +{ + const PixelFormatInfo &info = formatInfo[format]; + if (!info.compressed) return 0; + return (height + info.blockHeight - 1) / info.blockHeight; +} + +size_t getPixelFormatSliceSize(PixelFormat format, int width, int height) +{ + const PixelFormatInfo &info = formatInfo[format]; + size_t blockW = (width + info.blockWidth - 1) / info.blockWidth; + size_t blockH = (height + info.blockHeight - 1) / info.blockHeight; + return info.blockSize * blockW * blockH; } size_t getPixelFormatRowStride(PixelFormat format, int width) @@ -230,34 +321,7 @@ size_t getPixelFormatRowStride(PixelFormat format, int width) int getPixelFormatColorComponents(PixelFormat format) { - switch (format) - { - case PIXELFORMAT_R8_UNORM: - case PIXELFORMAT_R16_UNORM: - case PIXELFORMAT_R16_FLOAT: - case PIXELFORMAT_R32_FLOAT: - return 1; - case PIXELFORMAT_RG8_UNORM: - case PIXELFORMAT_RG16_UNORM: - case PIXELFORMAT_RG16_FLOAT: - case PIXELFORMAT_RG32_FLOAT: - case PIXELFORMAT_LA8_UNORM: - return 2; - case PIXELFORMAT_RGB565_UNORM: - case PIXELFORMAT_RG11B10_FLOAT: - return 3; - case PIXELFORMAT_RGBA8_UNORM: - case PIXELFORMAT_sRGBA8_UNORM: - case PIXELFORMAT_RGBA16_UNORM: - case PIXELFORMAT_RGBA16_FLOAT: - case PIXELFORMAT_RGBA32_FLOAT: - case PIXELFORMAT_RGBA4_UNORM: - case PIXELFORMAT_RGB5A1_UNORM: - case PIXELFORMAT_RGB10A2_UNORM: - return 4; - default: - return 0; - } + return formatInfo[format].components; } } // love diff --git a/src/common/pixelformat.h b/src/common/pixelformat.h index 4e4f09072..a1a4b1ddb 100644 --- a/src/common/pixelformat.h +++ b/src/common/pixelformat.h @@ -109,9 +109,23 @@ enum PixelFormat PIXELFORMAT_MAX_ENUM }; +struct PixelFormatInfo +{ + int components; + size_t blockWidth; + size_t blockHeight; + size_t blockSize; + bool color; + bool depth; + bool stencil; + bool compressed; +}; + bool getConstant(PixelFormat in, const char *&out); bool getConstant(const char *in, PixelFormat &out); +const PixelFormatInfo &getPixelFormatInfo(PixelFormat format); + /** * Gets whether the specified pixel format is a compressed type. **/ @@ -133,11 +147,46 @@ bool isPixelFormatDepth(PixelFormat format); bool isPixelFormatStencil(PixelFormat format); /** - * Gets the size in bytes of the specified pixel format. - * NOTE: Currently returns 0 for compressed formats. + * Gets the sRGB version of a linear pixel format, if applicable. **/ -size_t getPixelFormatSize(PixelFormat format); -size_t getPixelFormatRowStride(PixelFormat format, int width); +PixelFormat getSRGBPixelFormat(PixelFormat format); + +/** + * Gets the linear version of a sRGB pixel format, if applicable. + **/ +PixelFormat getLinearPixelFormat(PixelFormat format); + +/** + * Gets the block size in bytes of the specified pixel format. + * This is the size in bytes of a pixel for uncompressed formats, but *not* + * for compressed formats! + **/ +size_t getPixelFormatBlockSize(PixelFormat format); + +/** + * Gets the size in bytes of a row of an uncompressed pixel format. + **/ +size_t getPixelFormatUncompressedRowSize(PixelFormat format, int width); + +/** + * Gets the size in bytes of a row of a compressed pixel format. This is the + * number of blocks used by the given width, multiplied by the block size. The + * number of rows of blocks for a given height can be computed by + * getPixelFormatCompressedBlockRowCount. + **/ +size_t getPixelFormatCompressedBlockRowSize(PixelFormat format, int width); + +/** + * Gets the number of rows of blocks the given compressed pixel format will use, + * for the given height in pixels. + **/ +size_t getPixelFormatCompressedBlockRowCount(PixelFormat format, int height); + +/** + * Gets the size in bytes of a slice (width x height 2D plane) which uses the + * given pixel format. + **/ +size_t getPixelFormatSliceSize(PixelFormat format, int width, int height); /** * Gets the number of color components in the given pixel format. diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index d76bded00..befeb4bb8 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -300,6 +300,23 @@ double luax_numberflag(lua_State *L, int table_index, const char *key, double de return retval; } +bool luax_checkboolflag(lua_State *L, int table_index, const char *key) +{ + lua_getfield(L, table_index, key); + + bool retval = false; + if (lua_type(L, -1) != LUA_TBOOLEAN) + { + std::string err = "expected boolean field '" + std::string(key) + "' in table"; + return luaL_argerror(L, table_index, err.c_str()); + } + else + retval = luax_toboolean(L, -1); + lua_pop(L, 1); + + return retval; +} + int luax_checkintflag(lua_State *L, int table_index, const char *key) { lua_getfield(L, table_index, key); @@ -307,7 +324,7 @@ int luax_checkintflag(lua_State *L, int table_index, const char *key) int retval; if (!lua_isnumber(L, -1)) { - std::string err = "expected integer field " + std::string(key) + " in table"; + std::string err = "expected integer field '" + std::string(key) + "' in table"; return luaL_argerror(L, table_index, err.c_str()); } else diff --git a/src/common/runtime.h b/src/common/runtime.h index 5c884ebcd..c3e4574e5 100644 --- a/src/common/runtime.h +++ b/src/common/runtime.h @@ -190,6 +190,7 @@ 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); +bool luax_checkboolflag(lua_State *L, int table_index, const char *key); int luax_checkintflag(lua_State *L, int table_index, const char *key); /** diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 8f4b891df..59b779863 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -161,11 +161,11 @@ void Event::exceptionIfInRenderPass(const char *name) { // Some core OS graphics functionality (e.g. swap buffers on some platforms) // happens inside SDL_PumpEvents - which is called by SDL_PollEvent and - // friends. It's probably a bad idea to call those functions while a Canvas + // friends. It's probably a bad idea to call those functions while a RT // is active. auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr && gfx->isCanvasActive()) - throw love::Exception("%s cannot be called while a Canvas is active in love.graphics.", name); + if (gfx != nullptr && gfx->isRenderTargetActive()) + throw love::Exception("%s cannot be called while a render target is active in love.graphics.", name); } Message *Event::convert(const SDL_Event &e) diff --git a/src/modules/font/GlyphData.cpp b/src/modules/font/GlyphData.cpp index 640888322..69ec25ca8 100644 --- a/src/modules/font/GlyphData.cpp +++ b/src/modules/font/GlyphData.cpp @@ -78,7 +78,7 @@ void *GlyphData::getData() const size_t GlyphData::getPixelSize() const { - return getPixelFormatSize(format); + return getPixelFormatBlockSize(format); } void *GlyphData::getData(int x, int y) const diff --git a/src/modules/graphics/Canvas.cpp b/src/modules/graphics/Canvas.cpp deleted file mode 100644 index ce384450c..000000000 --- a/src/modules/graphics/Canvas.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Copyright (c) 2006-2020 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 "Canvas.h" -#include "Graphics.h" - -namespace love -{ -namespace graphics -{ - -love::Type Canvas::type("Canvas", &Texture::type); -int Canvas::canvasCount = 0; - -Canvas::Canvas(const Settings &settings) - : Texture(settings.type) -{ - this->settings = settings; - - width = settings.width; - height = settings.height; - pixelWidth = (int) ((width * settings.dpiScale) + 0.5); - pixelHeight = (int) ((height * settings.dpiScale) + 0.5); - - format = settings.format; - - if (texType == TEXTURE_VOLUME) - depth = settings.layers; - else if (texType == TEXTURE_2D_ARRAY) - layers = settings.layers; - - 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."); - - if (settings.readable.hasValue) - readable = settings.readable.value; - else - readable = !isPixelFormatDepthStencil(format); - - if (readable && isPixelFormatDepthStencil(format) && settings.msaa > 1) - throw love::Exception("Readable depth/stencil Canvases with MSAA are not currently supported."); - - if ((!readable || settings.msaa > 1) && settings.mipmaps != MIPMAPS_NONE) - throw love::Exception("Non-readable and MSAA textures cannot have mipmaps."); - - if (settings.mipmaps != MIPMAPS_NONE) - { - mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth); - filter.mipmap = defaultMipmapFilter; - } - - auto gfx = Module::getInstance(Module::M_GRAPHICS); - const Graphics::Capabilities &caps = gfx->getCapabilities(); - - if (!gfx->isCanvasFormatSupported(format, readable)) - { - const char *fstr = "rgba8"; - const char *readablestr = ""; - if (readable != !isPixelFormatDepthStencil(format)) - readablestr = readable ? " readable" : " non-readable"; - love::getConstant(format, fstr); - throw love::Exception("The %s%s canvas format is not supported by your graphics drivers.", fstr, readablestr); - } - - if (getRequestedMSAA() > 1 && texType != TEXTURE_2D) - throw love::Exception("MSAA is only supported for 2D texture types."); - - if (!readable && texType != TEXTURE_2D) - throw love::Exception("Non-readable pixel formats are only supported for 2D texture types."); - - if (!caps.textureTypes[texType]) - { - const char *textypestr = "unknown"; - Texture::getConstant(texType, textypestr); - throw love::Exception("%s textures are not supported on this system!", textypestr); - } - - validateDimensions(true); - - canvasCount++; -} - -Canvas::~Canvas() -{ - canvasCount--; -} - -Canvas::MipmapMode Canvas::getMipmapMode() const -{ - return settings.mipmaps; -} - -int Canvas::getRequestedMSAA() const -{ - return settings.msaa; -} - -love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r) -{ - if (!isReadable()) - throw love::Exception("Canvas:newImageData cannot be called on non-readable Canvases."); - - if (isPixelFormatDepthStencil(getPixelFormat())) - throw love::Exception("Canvas:newImageData cannot be called on Canvases with depth/stencil pixel formats."); - - if (r.x < 0 || r.y < 0 || r.w <= 0 || r.h <= 0 || (r.x + r.w) > getPixelWidth(mipmap) || (r.y + r.h) > getPixelHeight(mipmap)) - throw love::Exception("Invalid rectangle dimensions."); - - if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap)) - || (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."); - - PixelFormat dataformat = getPixelFormat(); - if (dataformat == PIXELFORMAT_sRGBA8_UNORM) - dataformat = PIXELFORMAT_RGBA8_UNORM; - - if (!image::ImageData::validPixelFormat(dataformat)) - { - const char *formatname = "unknown"; - love::getConstant(dataformat, formatname); - throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname); - } - - return module->newImageData(r.w, r.h, dataformat); -} - -void Canvas::draw(Graphics *gfx, Quad *q, const Matrix4 &t) -{ - if (gfx->isCanvasActive(this)) - throw love::Exception("Cannot render a Canvas to itself!"); - - Texture::draw(gfx, q, t); -} - -void Canvas::drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m) -{ - if (gfx->isCanvasActive(this, layer)) - throw love::Exception("Cannot render a Canvas to itself!"); - - Texture::drawLayer(gfx, layer, quad, m); -} - -bool Canvas::getConstant(const char *in, MipmapMode &out) -{ - return mipmapModes.find(in, out); -} - -bool Canvas::getConstant(MipmapMode in, const char *&out) -{ - return mipmapModes.find(in, out); -} - -std::vector Canvas::getConstants(MipmapMode) -{ - return mipmapModes.getNames(); -} - -bool Canvas::getConstant(const char *in, SettingType &out) -{ - return settingTypes.find(in, out); -} - -bool Canvas::getConstant(SettingType in, const char *&out) -{ - return settingTypes.find(in, out); -} - -const char *Canvas::getConstant(SettingType in) -{ - const char *name = nullptr; - getConstant(in, name); - return name; -} - -std::vector Canvas::getConstants(SettingType) -{ - return settingTypes.getNames(); -} - -StringMap::Entry Canvas::mipmapEntries[] = -{ - { "none", MIPMAPS_NONE }, - { "manual", MIPMAPS_MANUAL }, - { "auto", MIPMAPS_AUTO }, -}; - -StringMap Canvas::mipmapModes(Canvas::mipmapEntries, sizeof(Canvas::mipmapEntries)); - -StringMap::Entry Canvas::settingTypeEntries[] = -{ - // Width / height / layers are currently omittted because they're separate - // arguments to newCanvas in the wrapper code. - { "mipmaps", SETTING_MIPMAPS }, - { "format", SETTING_FORMAT }, - { "type", SETTING_TYPE }, - { "dpiscale", SETTING_DPI_SCALE }, - { "msaa", SETTING_MSAA }, - { "readable", SETTING_READABLE }, -}; - -StringMap Canvas::settingTypes(Canvas::settingTypeEntries, sizeof(Canvas::settingTypeEntries)); - -} // graphics -} // love - diff --git a/src/modules/graphics/Canvas.h b/src/modules/graphics/Canvas.h deleted file mode 100644 index 8171f5f54..000000000 --- a/src/modules/graphics/Canvas.h +++ /dev/null @@ -1,118 +0,0 @@ -/** -* Copyright (c) 2006-2020 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. -**/ - -#pragma once - -#include "image/Image.h" -#include "image/ImageData.h" -#include "Texture.h" -#include "common/Optional.h" -#include "common/StringMap.h" - -namespace love -{ -namespace graphics -{ - -class Graphics; - -class Canvas : public Texture -{ -public: - - static love::Type type; - - enum MipmapMode - { - MIPMAPS_NONE, - MIPMAPS_MANUAL, - MIPMAPS_AUTO, - MIPMAPS_MAX_ENUM - }; - - enum SettingType - { - SETTING_WIDTH, - SETTING_HEIGHT, - SETTING_LAYERS, - SETTING_MIPMAPS, - SETTING_FORMAT, - SETTING_TYPE, - SETTING_DPI_SCALE, - SETTING_MSAA, - SETTING_READABLE, - SETTING_MAX_ENUM - }; - - struct Settings - { - int width = 1; - int height = 1; - int layers = 1; // depth for 3D textures - MipmapMode mipmaps = MIPMAPS_NONE; - PixelFormat format = PIXELFORMAT_NORMAL; - TextureType type = TEXTURE_2D; - float dpiScale = 1.0f; - int msaa = 0; - OptionalBool readable; - }; - - Canvas(const Settings &settings); - virtual ~Canvas(); - - MipmapMode getMipmapMode() const; - int getRequestedMSAA() const; - - virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect); - virtual void generateMipmaps() = 0; - - virtual int getMSAA() const = 0; - virtual ptrdiff_t getRenderTargetHandle() const = 0; - - void draw(Graphics *gfx, Quad *q, const Matrix4 &t) override; - void drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &t) override; - - static int canvasCount; - - static bool getConstant(const char *in, MipmapMode &out); - static bool getConstant(MipmapMode in, const char *&out); - static std::vector getConstants(MipmapMode); - - static bool getConstant(const char *in, SettingType &out); - static bool getConstant(SettingType in, const char *&out); - static const char *getConstant(SettingType in); - static std::vector getConstants(SettingType); - -protected: - - Settings settings; - -private: - - static StringMap::Entry mipmapEntries[]; - static StringMap mipmapModes; - - static StringMap::Entry settingTypeEntries[]; - static StringMap settingTypes; - -}; // Canvas - -} // graphics -} // love diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index f94adaa05..928d96f7d 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -47,18 +47,20 @@ int Font::fontCount = 0; const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub; -Font::Font(love::font::Rasterizer *r, const Texture::Filter &f) +Font::Font(love::font::Rasterizer *r, const SamplerState &s) : rasterizers({r}) , height(r->getHeight()) , lineHeight(1) , textureWidth(128) , textureHeight(128) - , filter(f) + , samplerState() , dpiScale(r->getDPIScale()) , useSpacesAsTab(false) , textureCacheID(0) { - filter.mipmap = Texture::FILTER_NONE; + samplerState.minFilter = s.minFilter; + samplerState.magFilter = s.magFilter; + samplerState.maxAnisotropy = s.maxAnisotropy; // Try to find the best texture size match for the font size. default to the // largest texture size if no rough match is found. @@ -123,7 +125,7 @@ bool Font::loadVolatile() { textureCacheID++; glyphs.clear(); - images.clear(); + textures.clear(); createTexture(); return true; } @@ -133,7 +135,7 @@ void Font::createTexture() auto gfx = Module::getInstance(Module::M_GRAPHICS); gfx->flushStreamDraws(); - Image *image = nullptr; + Texture *texture = nullptr; TextureSize size = {textureWidth, textureHeight}; TextureSize nextsize = getNextTextureSize(); bool recreatetexture = false; @@ -141,19 +143,22 @@ void Font::createTexture() // 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()) + if ((nextsize.width > size.width || nextsize.height > size.height) && !textures.empty()) { recreatetexture = true; size = nextsize; - images.pop_back(); + textures.pop_back(); } - Image::Settings settings; - image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings); - image->setFilter(filter); + Texture::Settings settings; + settings.format = pixelFormat; + settings.width = size.width; + settings.height = size.height; + texture = gfx->newTexture(settings, nullptr); + texture->setSamplerState(samplerState); { - size_t bpp = getPixelFormatSize(pixelFormat); + size_t bpp = getPixelFormatBlockSize(pixelFormat); size_t pixelcount = size.width * size.height; // Initialize the texture with transparent white for Luminance-Alpha @@ -168,10 +173,10 @@ void Font::createTexture() } Rect rect = {0, 0, size.width, size.height}; - image->replacePixels(emptydata.data(), emptydata.size(), 0, 0, rect, false); + texture->replacePixels(emptydata.data(), emptydata.size(), 0, 0, rect, false); } - images.emplace_back(image, Acquire::NORETAIN); + textures.emplace_back(texture, Acquire::NORETAIN); textureWidth = size.width; textureHeight = size.height; @@ -198,7 +203,7 @@ void Font::createTexture() void Font::unloadVolatile() { glyphs.clear(); - images.clear(); + textures.clear(); } love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph) @@ -266,11 +271,11 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) // Don't waste space for empty glyphs. if (w > 0 && h > 0) { - Image *image = images.back(); - g.texture = image; + Texture *texture = textures.back(); + g.texture = texture; Rect rect = {textureX, textureY, gd->getWidth(), gd->getHeight()}; - image->replacePixels(gd->getData(), gd->getSize(), 0, 0, rect, false); + texture->replacePixels(gd->getData(), gd->getSize(), 0, 0, rect, false); double tX = (double) textureX, tY = (double) textureY; double tWidth = (double) textureWidth, tHeight = (double) textureHeight; @@ -918,17 +923,19 @@ float Font::getLineHeight() const return lineHeight; } -void Font::setFilter(const Texture::Filter &f) +void Font::setSamplerState(const SamplerState &s) { - for (const auto &image : images) - image->setFilter(f); + samplerState.minFilter = s.minFilter; + samplerState.magFilter = s.magFilter; + samplerState.maxAnisotropy = s.maxAnisotropy; - filter = f; + for (const auto &texture : textures) + texture->setSamplerState(samplerState); } -const Texture::Filter &Font::getFilter() const +const SamplerState &Font::getSamplerState() const { - return filter; + return samplerState; } int Font::getAscent() const diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index dbca1920c..b1bbee8d7 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -33,7 +33,7 @@ #include "common/Vector.h" #include "font/Rasterizer.h" -#include "Image.h" +#include "Texture.h" #include "vertex.h" #include "Volatile.h" @@ -96,7 +96,7 @@ public: int vertexcount; }; - Font(love::font::Rasterizer *r, const Texture::Filter &filter); + Font(love::font::Rasterizer *r, const SamplerState &samplerState); virtual ~Font(); @@ -158,8 +158,8 @@ public: **/ float getLineHeight() const; - void setFilter(const Texture::Filter &f); - const Texture::Filter &getFilter() const; + void setSamplerState(const SamplerState &s); + const SamplerState &getSamplerState() const; // Extra font metrics int getAscent() const; @@ -217,7 +217,7 @@ private: int textureWidth; int textureHeight; - std::vector> images; + std::vector> textures; // maps glyphs to glyph texture information std::unordered_map glyphs; @@ -227,7 +227,7 @@ private: PixelFormat pixelFormat; - Texture::Filter filter; + SamplerState samplerState; float dpiScale; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 9572bded2..e2741f87d 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -147,7 +147,7 @@ Graphics::Graphics() , writingToStencil(false) , streamBufferState() , projectionMatrix() - , canvasSwitchCount(0) + , renderTargetSwitchCount(0) , drawCalls(0) , drawCallsBatched(0) , quadIndexBuffer(nullptr) @@ -215,19 +215,19 @@ 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) +Font *Graphics::newFont(love::font::Rasterizer *data) { - return new Font(data, filter); + return new Font(data, states.back().defaultSamplerState); } -Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter) +Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting) { auto fontmodule = Module::getInstance(M_FONT); if (!fontmodule) throw love::Exception("Font module has not been loaded."); StrongRef r(fontmodule->newTrueTypeRasterizer(size, hinting), Acquire::NORETAIN); - return newFont(r.get(), filter); + return newFont(r.get()); } Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale) @@ -366,8 +366,8 @@ int Graphics::getPixelHeight() const double Graphics::getCurrentDPIScale() const { const auto &rt = states.back().renderTargets.getFirstTarget(); - if (rt.canvas.get()) - return rt.canvas->getDPIScale(); + if (rt.texture.get()) + return rt.texture->getDPIScale(); return getScreenDPIScale(); } @@ -428,13 +428,12 @@ void Graphics::restoreState(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - setCanvas(s.renderTargets); + setRenderTargets(s.renderTargets); setColorMask(s.colorMask); setWireframe(s.wireframe); - setDefaultFilter(s.defaultFilter); - setDefaultMipmapFilter(s.defaultMipmapFilter, s.defaultMipmapSharpness); + setDefaultSamplerState(s.defaultSamplerState); } void Graphics::restoreStateChecked(const DisplayState &s) @@ -482,27 +481,27 @@ void Graphics::restoreStateChecked(const DisplayState &s) const auto &sRTs = s.renderTargets; const auto &curRTs = cur.renderTargets; - bool canvaseschanged = sRTs.colors.size() != curRTs.colors.size(); - if (!canvaseschanged) + bool rtschanged = sRTs.colors.size() != curRTs.colors.size(); + if (!rtschanged) { for (size_t i = 0; i < sRTs.colors.size() && i < curRTs.colors.size(); i++) { if (sRTs.colors[i] != curRTs.colors[i]) { - canvaseschanged = true; + rtschanged = true; break; } } - if (!canvaseschanged && sRTs.depthStencil != curRTs.depthStencil) - canvaseschanged = true; + if (!rtschanged && sRTs.depthStencil != curRTs.depthStencil) + rtschanged = true; if (sRTs.temporaryRTFlags != curRTs.temporaryRTFlags) - canvaseschanged = true; + rtschanged = true; } - if (canvaseschanged) - setCanvas(s.renderTargets); + if (rtschanged) + setRenderTargets(s.renderTargets); if (s.colorMask != cur.colorMask) setColorMask(s.colorMask); @@ -510,8 +509,7 @@ void Graphics::restoreStateChecked(const DisplayState &s) if (s.wireframe != cur.wireframe) setWireframe(s.wireframe); - setDefaultFilter(s.defaultFilter); - setDefaultMipmapFilter(s.defaultMipmapFilter, s.defaultMipmapSharpness); + setDefaultSamplerState(s.defaultSamplerState); } Colorf Graphics::getColor() const @@ -576,50 +574,50 @@ love::graphics::Shader *Graphics::getShader() const return states.back().shader.get(); } -void Graphics::setCanvas(RenderTarget rt, uint32 temporaryRTFlags) +void Graphics::setRenderTarget(RenderTarget rt, uint32 temporaryRTFlags) { - if (rt.canvas == nullptr) - return setCanvas(); + if (rt.texture == nullptr) + return setRenderTarget(); RenderTargets rts; rts.colors.push_back(rt); rts.temporaryRTFlags = temporaryRTFlags; - setCanvas(rts); + setRenderTargets(rts); } -void Graphics::setCanvas(const RenderTargetsStrongRef &rts) +void Graphics::setRenderTargets(const RenderTargetsStrongRef &rts) { RenderTargets targets; targets.colors.reserve(rts.colors.size()); for (const auto &rt : rts.colors) - targets.colors.emplace_back(rt.canvas.get(), rt.slice, rt.mipmap); + targets.colors.emplace_back(rt.texture.get(), rt.slice, rt.mipmap); - targets.depthStencil = RenderTarget(rts.depthStencil.canvas, rts.depthStencil.slice, rts.depthStencil.mipmap); + targets.depthStencil = RenderTarget(rts.depthStencil.texture, rts.depthStencil.slice, rts.depthStencil.mipmap); targets.temporaryRTFlags = rts.temporaryRTFlags; - return setCanvas(targets); + return setRenderTargets(targets); } -void Graphics::setCanvas(const RenderTargets &rts) +void Graphics::setRenderTargets(const RenderTargets &rts) { DisplayState &state = states.back(); - int ncanvases = (int) rts.colors.size(); + int rtcount = (int) rts.colors.size(); RenderTarget firsttarget = rts.getFirstTarget(); - love::graphics::Canvas *firstcanvas = firsttarget.canvas; + Texture *firsttex = firsttarget.texture; - if (firstcanvas == nullptr) - return setCanvas(); + if (firsttex == nullptr) + return setRenderTarget(); const auto &prevRTs = state.renderTargets; - if (ncanvases == (int) prevRTs.colors.size()) + if (rtcount == (int) prevRTs.colors.size()) { bool modified = false; - for (int i = 0; i < ncanvases; i++) + for (int i = 0; i < rtcount; i++) { if (rts.colors[i] != prevRTs.colors[i]) { @@ -638,36 +636,42 @@ void Graphics::setCanvas(const RenderTargets &rts) return; } - if (ncanvases > capabilities.limits[LIMIT_MULTI_CANVAS]) - throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases); + if (rtcount > capabilities.limits[LIMIT_RENDER_TARGETS]) + throw love::Exception("This system can't simultaneously render to %d textures.", rtcount); - bool multiformatsupported = capabilities.features[FEATURE_MULTI_CANVAS_FORMATS]; + bool multiformatsupported = capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS]; PixelFormat firstcolorformat = PIXELFORMAT_UNKNOWN; if (!rts.colors.empty()) - firstcolorformat = rts.colors[0].canvas->getPixelFormat(); + firstcolorformat = rts.colors[0].texture->getPixelFormat(); + + if (!firsttex->isRenderTarget()) + throw love::Exception("Texture must be created as a render target to be used in setRenderTargets."); if (isPixelFormatDepthStencil(firstcolorformat)) - throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas."); + throw love::Exception("Depth/stencil format textures must be used with the 'depthstencil' field of the table passed into setRenderTargets."); - if (firsttarget.mipmap < 0 || firsttarget.mipmap >= firstcanvas->getMipmapCount()) + if (firsttarget.mipmap < 0 || firsttarget.mipmap >= firsttex->getMipmapCount()) throw love::Exception("Invalid mipmap level %d.", firsttarget.mipmap + 1); - if (!firstcanvas->isValidSlice(firsttarget.slice)) + if (!firsttex->isValidSlice(firsttarget.slice)) throw love::Exception("Invalid slice index: %d.", firsttarget.slice + 1); - bool hasSRGBcanvas = firstcolorformat == PIXELFORMAT_sRGBA8_UNORM; - int pixelw = firstcanvas->getPixelWidth(firsttarget.mipmap); - int pixelh = firstcanvas->getPixelHeight(firsttarget.mipmap); - int reqmsaa = firstcanvas->getRequestedMSAA(); + bool hasSRGBtexture = firstcolorformat == PIXELFORMAT_sRGBA8_UNORM; + int pixelw = firsttex->getPixelWidth(firsttarget.mipmap); + int pixelh = firsttex->getPixelHeight(firsttarget.mipmap); + int reqmsaa = firsttex->getRequestedMSAA(); - for (int i = 1; i < ncanvases; i++) + for (int i = 1; i < rtcount; i++) { - love::graphics::Canvas *c = rts.colors[i].canvas; + Texture *c = rts.colors[i].texture; PixelFormat format = c->getPixelFormat(); int mip = rts.colors[i].mipmap; int slice = rts.colors[i].slice; + if (!c->isRenderTarget()) + throw love::Exception("Texture must be created as a render target to be used in setRenderTargets."); + if (mip < 0 || mip >= c->getMipmapCount()) throw love::Exception("Invalid mipmap level %d.", mip + 1); @@ -675,35 +679,38 @@ void Graphics::setCanvas(const RenderTargets &rts) throw love::Exception("Invalid slice index: %d.", slice + 1); if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh) - throw love::Exception("All canvases must have the same pixel dimensions."); + throw love::Exception("All textures must have the same pixel dimensions."); if (!multiformatsupported && format != firstcolorformat) - throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); + throw love::Exception("This system doesn't support multi-render-target rendering with different texture formats."); if (c->getRequestedMSAA() != reqmsaa) - throw love::Exception("All Canvases must have the same MSAA value."); + throw love::Exception("All textures must have the same MSAA value."); if (isPixelFormatDepthStencil(format)) - throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas."); + throw love::Exception("Depth/stencil format textures must be used with the 'depthstencil' field of the table passed into setRenderTargets."); if (format == PIXELFORMAT_sRGBA8_UNORM) - hasSRGBcanvas = true; + hasSRGBtexture = true; } - if (rts.depthStencil.canvas != nullptr) + if (rts.depthStencil.texture != nullptr) { - love::graphics::Canvas *c = rts.depthStencil.canvas; + Texture *c = rts.depthStencil.texture; int mip = rts.depthStencil.mipmap; int slice = rts.depthStencil.slice; + if (!c->isRenderTarget()) + throw love::Exception("Texture must be created as a render target to be used in setRenderTargets."); + if (!isPixelFormatDepthStencil(c->getPixelFormat())) - throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas."); + throw love::Exception("Only depth/stencil format textures can be used with the 'depthstencil' field of the table passed into setRenderTargets."); if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh) - throw love::Exception("All canvases must have the same pixel dimensions."); + throw love::Exception("All Textures must have the same pixel dimensions."); - if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) - throw love::Exception("All Canvases must have the same MSAA value."); + if (c->getRequestedMSAA() != firsttex->getRequestedMSAA()) + throw love::Exception("All Textures must have the same MSAA value."); if (mip < 0 || mip >= c->getMipmapCount()) throw love::Exception("Invalid mipmap level %d.", mip + 1); @@ -712,12 +719,12 @@ void Graphics::setCanvas(const RenderTargets &rts) throw love::Exception("Invalid slice index: %d.", slice + 1); } - int w = firstcanvas->getWidth(firsttarget.mipmap); - int h = firstcanvas->getHeight(firsttarget.mipmap); + int w = firsttex->getWidth(firsttarget.mipmap); + int h = firsttex->getHeight(firsttarget.mipmap); flushStreamDraws(); - if (rts.depthStencil.canvas == nullptr && rts.temporaryRTFlags != 0) + if (rts.depthStencil.texture == nullptr && rts.temporaryRTFlags != 0) { bool wantsdepth = (rts.temporaryRTFlags & TEMPORARY_RT_DEPTH) != 0; bool wantsstencil = (rts.temporaryRTFlags & TEMPORARY_RT_STENCIL) != 0; @@ -725,54 +732,54 @@ void Graphics::setCanvas(const RenderTargets &rts) PixelFormat dsformat = PIXELFORMAT_STENCIL8; if (wantsdepth && wantsstencil) dsformat = PIXELFORMAT_DEPTH24_UNORM_STENCIL8; - else if (wantsdepth && isCanvasFormatSupported(PIXELFORMAT_DEPTH24_UNORM, false)) + else if (wantsdepth && isPixelFormatSupported(PIXELFORMAT_DEPTH24_UNORM, true, false, false)) dsformat = PIXELFORMAT_DEPTH24_UNORM; else if (wantsdepth) dsformat = PIXELFORMAT_DEPTH16_UNORM; else if (wantsstencil) dsformat = PIXELFORMAT_STENCIL8; - // We want setCanvasInternal to have a pointer to the temporary RT, but - // we don't want to directly store it in the main graphics state. + // We want setRenderTargetsInternal to have a pointer to the temporary RT, + // but we don't want to directly store it in the main graphics state. RenderTargets realRTs = rts; - realRTs.depthStencil.canvas = getTemporaryCanvas(dsformat, pixelw, pixelh, reqmsaa); + realRTs.depthStencil.texture = getTemporaryTexture(dsformat, pixelw, pixelh, reqmsaa); realRTs.depthStencil.slice = 0; - setCanvasInternal(realRTs, w, h, pixelw, pixelh, hasSRGBcanvas); + setRenderTargetsInternal(realRTs, w, h, pixelw, pixelh, hasSRGBtexture); } else - setCanvasInternal(rts, w, h, pixelw, pixelh, hasSRGBcanvas); + setRenderTargetsInternal(rts, w, h, pixelw, pixelh, hasSRGBtexture); RenderTargetsStrongRef refs; refs.colors.reserve(rts.colors.size()); for (auto c : rts.colors) - refs.colors.emplace_back(c.canvas, c.slice, c.mipmap); + refs.colors.emplace_back(c.texture, c.slice, c.mipmap); - refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice); + refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.texture, rts.depthStencil.slice); refs.temporaryRTFlags = rts.temporaryRTFlags; std::swap(state.renderTargets, refs); - canvasSwitchCount++; + renderTargetSwitchCount++; } -void Graphics::setCanvas() +void Graphics::setRenderTarget() { DisplayState &state = states.back(); - if (state.renderTargets.colors.empty() && state.renderTargets.depthStencil.canvas == nullptr) + if (state.renderTargets.colors.empty() && state.renderTargets.depthStencil.texture == nullptr) return; flushStreamDraws(); - setCanvasInternal(RenderTargets(), width, height, pixelWidth, pixelHeight, isGammaCorrect()); + setRenderTargetsInternal(RenderTargets(), width, height, pixelWidth, pixelHeight, isGammaCorrect()); state.renderTargets = RenderTargetsStrongRef(); - canvasSwitchCount++; + renderTargetSwitchCount++; } -Graphics::RenderTargets Graphics::getCanvas() const +Graphics::RenderTargets Graphics::getRenderTargets() const { const auto &curRTs = states.back().renderTargets; @@ -780,82 +787,83 @@ Graphics::RenderTargets Graphics::getCanvas() const rts.colors.reserve(curRTs.colors.size()); for (const auto &rt : curRTs.colors) - rts.colors.emplace_back(rt.canvas.get(), rt.slice, rt.mipmap); + rts.colors.emplace_back(rt.texture.get(), rt.slice, rt.mipmap); - rts.depthStencil = RenderTarget(curRTs.depthStencil.canvas, curRTs.depthStencil.slice, curRTs.depthStencil.mipmap); + rts.depthStencil = RenderTarget(curRTs.depthStencil.texture, curRTs.depthStencil.slice, curRTs.depthStencil.mipmap); rts.temporaryRTFlags = curRTs.temporaryRTFlags; return rts; } -bool Graphics::isCanvasActive() const +bool Graphics::isRenderTargetActive() const { const auto &rts = states.back().renderTargets; - return !rts.colors.empty() || rts.depthStencil.canvas != nullptr; + return !rts.colors.empty() || rts.depthStencil.texture != nullptr; } -bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const +bool Graphics::isRenderTargetActive(Texture *texture) const { const auto &rts = states.back().renderTargets; for (const auto &rt : rts.colors) { - if (rt.canvas.get() == canvas) + if (rt.texture.get() == texture) return true; } - if (rts.depthStencil.canvas.get() == canvas) + if (rts.depthStencil.texture.get() == texture) return true; return false; } -bool Graphics::isCanvasActive(Canvas *canvas, int slice) const +bool Graphics::isRenderTargetActive(Texture *texture, int slice) const { const auto &rts = states.back().renderTargets; for (const auto &rt : rts.colors) { - if (rt.canvas.get() == canvas && rt.slice == slice) + if (rt.texture.get() == texture && rt.slice == slice) return true; } - if (rts.depthStencil.canvas.get() == canvas && rts.depthStencil.slice == slice) + if (rts.depthStencil.texture.get() == texture && rts.depthStencil.slice == slice) return true; return false; } -Canvas *Graphics::getTemporaryCanvas(PixelFormat format, int w, int h, int samples) +Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int samples) { - love::graphics::Canvas *canvas = nullptr; + Texture *texture = nullptr; - for (TemporaryCanvas &temp : temporaryCanvases) + for (TemporaryTexture &temp : temporaryTextures) { - Canvas *c = temp.canvas; + Texture *c = temp.texture; if (c->getPixelFormat() == format && c->getPixelWidth() == w && c->getPixelHeight() == h && c->getRequestedMSAA() == samples) { - canvas = c; + texture = c; temp.framesSinceUse = 0; break; } } - if (canvas == nullptr) + if (texture == nullptr) { - Canvas::Settings settings; + Texture::Settings settings; + settings.renderTarget = true; settings.format = format; settings.width = w; settings.height = h; settings.msaa = samples; - canvas = newCanvas(settings); + texture = newTexture(settings); - temporaryCanvases.emplace_back(canvas); + temporaryTextures.emplace_back(texture); } - return canvas; + return texture; } void Graphics::intersectScissor(const Rect &rect) @@ -954,30 +962,14 @@ const BlendState &Graphics::getBlendState() const return states.back().blend; } -void Graphics::setDefaultFilter(const Texture::Filter &f) +void Graphics::setDefaultSamplerState(const SamplerState &s) { - Texture::defaultFilter = f; - states.back().defaultFilter = f; + states.back().defaultSamplerState = s; } -const Texture::Filter &Graphics::getDefaultFilter() const +const SamplerState &Graphics::getDefaultSamplerState() const { - return Texture::defaultFilter; -} - -void Graphics::setDefaultMipmapFilter(Texture::FilterMode filter, float sharpness) -{ - Texture::defaultMipmapFilter = filter; - Texture::defaultMipmapSharpness = sharpness; - - states.back().defaultMipmapFilter = filter; - states.back().defaultMipmapSharpness = sharpness; -} - -void Graphics::getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpness) const -{ - *filter = Texture::defaultMipmapFilter; - *sharpness = Texture::defaultMipmapSharpness; + return states.back().defaultSamplerState; } void Graphics::setLineWidth(float width) @@ -1640,10 +1632,9 @@ Graphics::Stats Graphics::getStats() const if (streamBufferState.vertexCount > 0) stats.drawCalls++; - stats.canvasSwitches = canvasSwitchCount; + stats.renderTargetSwitches = renderTargetSwitchCount; stats.drawCallsBatched = drawCallsBatched; - stats.canvases = Canvas::canvasCount; - stats.images = Image::imageCount; + stats.textures = Texture::textureCount; stats.fonts = Font::fontCount; stats.textureMemory = Texture::totalGraphicsMemory; @@ -1926,16 +1917,16 @@ StringMap Graphics::lineJoins( StringMap::Entry Graphics::featureEntries[] = { - { "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS }, - { "clampzero", FEATURE_CLAMP_ZERO }, - { "blendminmax", FEATURE_BLENDMINMAX }, - { "lighten", FEATURE_LIGHTEN }, - { "fullnpot", FEATURE_FULL_NPOT }, - { "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP }, - { "shaderderivatives", FEATURE_SHADER_DERIVATIVES }, - { "glsl3", FEATURE_GLSL3 }, - { "glsl4", FEATURE_GLSL4 }, - { "instancing", FEATURE_INSTANCING }, + { "multirendertargetformats", FEATURE_MULTI_RENDER_TARGET_FORMATS }, + { "clampzero", FEATURE_CLAMP_ZERO }, + { "blendminmax", FEATURE_BLEND_MINMAX }, + { "lighten", FEATURE_LIGHTEN }, + { "fullnpot", FEATURE_FULL_NPOT }, + { "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP }, + { "shaderderivatives", FEATURE_SHADER_DERIVATIVES }, + { "glsl3", FEATURE_GLSL3 }, + { "glsl4", FEATURE_GLSL4 }, + { "instancing", FEATURE_INSTANCING }, }; StringMap Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries)); @@ -1947,8 +1938,8 @@ StringMap::Entry Graphics::syst { "texturelayers", LIMIT_TEXTURE_LAYERS }, { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE }, { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE }, - { "multicanvas", LIMIT_MULTI_CANVAS }, - { "canvasmsaa", LIMIT_CANVAS_MSAA }, + { "rendertargets", LIMIT_RENDER_TARGETS }, + { "texturemsaa", LIMIT_TEXTURE_MSAA }, { "anisotropy", LIMIT_ANISOTROPY }, }; diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index a9aec2cd4..256804842 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -32,13 +32,11 @@ #include "StreamBuffer.h" #include "vertex.h" #include "Texture.h" -#include "Canvas.h" #include "Font.h" #include "ShaderStage.h" #include "Shader.h" #include "Quad.h" #include "Mesh.h" -#include "Image.h" #include "Deprecations.h" #include "renderstate.h" #include "math/Transform.h" @@ -135,9 +133,9 @@ public: enum Feature { - FEATURE_MULTI_CANVAS_FORMATS, + FEATURE_MULTI_RENDER_TARGET_FORMATS, FEATURE_CLAMP_ZERO, - FEATURE_BLENDMINMAX, + FEATURE_BLEND_MINMAX, FEATURE_LIGHTEN, // Deprecated FEATURE_FULL_NPOT, FEATURE_PIXEL_SHADER_HIGHP, @@ -162,8 +160,8 @@ public: LIMIT_VOLUME_TEXTURE_SIZE, LIMIT_CUBE_TEXTURE_SIZE, LIMIT_TEXTURE_LAYERS, - LIMIT_MULTI_CANVAS, - LIMIT_CANVAS_MSAA, + LIMIT_RENDER_TARGETS, + LIMIT_TEXTURE_MSAA, LIMIT_ANISOTROPY, LIMIT_MAX_ENUM }; @@ -200,10 +198,9 @@ public: { int drawCalls; int drawCallsBatched; - int canvasSwitches; + int renderTargetSwitches; int shaderSwitches; - int canvases; - int images; + int textures; int fonts; int64 textureMemory; }; @@ -316,53 +313,53 @@ public: struct RenderTarget { - Canvas *canvas; + Texture *texture; int slice; int mipmap; - RenderTarget(Canvas *canvas, int slice = 0, int mipmap = 0) - : canvas(canvas) + RenderTarget(Texture *texture, int slice = 0, int mipmap = 0) + : texture(texture) , slice(slice) , mipmap(mipmap) {} RenderTarget() - : canvas(nullptr) + : texture(nullptr) , slice(0) , mipmap(0) {} bool operator != (const RenderTarget &other) const { - return canvas != other.canvas || slice != other.slice || mipmap != other.mipmap; + return texture != other.texture || slice != other.slice || mipmap != other.mipmap; } bool operator != (const RenderTargetStrongRef &other) const { - return canvas != other.canvas.get() || slice != other.slice || mipmap != other.mipmap; + return texture != other.texture.get() || slice != other.slice || mipmap != other.mipmap; } }; struct RenderTargetStrongRef { - StrongRef canvas; + StrongRef texture; int slice = 0; int mipmap = 0; - RenderTargetStrongRef(Canvas *canvas, int slice = 0, int mipmap = 0) - : canvas(canvas) + RenderTargetStrongRef(Texture *texture, int slice = 0, int mipmap = 0) + : texture(texture) , slice(slice) , mipmap(mipmap) {} bool operator != (const RenderTargetStrongRef &other) const { - return canvas.get() != other.canvas.get() || slice != other.slice || mipmap != other.mipmap; + return texture.get() != other.texture.get() || slice != other.slice || mipmap != other.mipmap; } bool operator != (const RenderTarget &other) const { - return canvas.get() != other.canvas || slice != other.slice || mipmap != other.mipmap; + return texture.get() != other.texture || slice != other.slice || mipmap != other.mipmap; } }; @@ -429,19 +426,16 @@ public: // Implements Module. virtual ModuleType getModuleType() const { return M_GRAPHICS; } - 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; + virtual Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) = 0; Quad *newQuad(Quad::Viewport v, double sw, double sh); - Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter); - Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter = Texture::defaultFilter); + Font *newFont(love::font::Rasterizer *data); + Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting); Video *newVideo(love::video::VideoStream *stream, float dpiscale); SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage); ParticleSystem *newParticleSystem(Texture *texture, int size); - virtual Canvas *newCanvas(const Canvas::Settings &settings) = 0; - ShaderStage *newShaderStage(ShaderStage::StageType stage, const std::string &source); Shader *newShader(const std::string &vertex, const std::string &pixel); @@ -544,15 +538,15 @@ public: Shader *getShader() const; - void setCanvas(RenderTarget rt, uint32 temporaryRTFlags); - void setCanvas(const RenderTargets &rts); - void setCanvas(const RenderTargetsStrongRef &rts); - void setCanvas(); + void setRenderTarget(RenderTarget rt, uint32 temporaryRTFlags); + void setRenderTargets(const RenderTargets &rts); + void setRenderTargets(const RenderTargetsStrongRef &rts); + void setRenderTarget(); - RenderTargets getCanvas() const; - bool isCanvasActive() const; - bool isCanvasActive(Canvas *canvas) const; - bool isCanvasActive(Canvas *canvas, int slice) const; + RenderTargets getRenderTargets() const; + bool isRenderTargetActive() const; + bool isRenderTargetActive(Texture *texture) const; + bool isRenderTargetActive(Texture *texture, int slice) const; /** * Scissor defines a box such that everything outside that box is discarded @@ -620,20 +614,14 @@ public: const BlendState &getBlendState() const; /** - * Sets the default filter for images, canvases, and fonts. + * Sets the default sampler state for textures, videos, and fonts. **/ - void setDefaultFilter(const Texture::Filter &f); + void setDefaultSamplerState(const SamplerState &s); /** - * Gets the default filter for images, canvases, and fonts. + * Gets the default sampler state for textures, videos, and fonts. **/ - const Texture::Filter &getDefaultFilter() const; - - /** - * Default Image mipmap filter mode and sharpness values. - **/ - void setDefaultMipmapFilter(Texture::FilterMode filter, float sharpness); - void getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpness) const; + const SamplerState &getDefaultSamplerState() const; /** * Sets the line width. @@ -785,12 +773,14 @@ public: const Capabilities &getCapabilities() const; /** - * Gets whether the specified pixel format is supported by Canvases or - * Images. + * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format. **/ - virtual bool isCanvasFormatSupported(PixelFormat format) const = 0; - virtual bool isCanvasFormatSupported(PixelFormat format, bool readable) const = 0; - virtual bool isImageFormatSupported(PixelFormat format, bool sRGB = false) const = 0; + virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const = 0; + + /** + * Gets whether the specified pixel format is supported. + **/ + virtual bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) = 0; /** * Gets the renderer used by love.graphics. @@ -925,10 +915,7 @@ protected: bool wireframe = false; - Texture::Filter defaultFilter = Texture::Filter(); - - Texture::FilterMode defaultMipmapFilter = Texture::FILTER_LINEAR; - float defaultMipmapSharpness = 0.0f; + SamplerState defaultSamplerState = SamplerState(); }; struct StreamBufferState @@ -954,13 +941,13 @@ protected: } }; - struct TemporaryCanvas + struct TemporaryTexture { - Canvas *canvas; + Texture *texture; int framesSinceUse; - TemporaryCanvas(Canvas *c) - : canvas(c) + TemporaryTexture(Texture *tex) + : texture(tex) , framesSinceUse(0) {} }; @@ -969,14 +956,14 @@ protected: virtual Shader *newShaderInternal(ShaderStage *vertex, ShaderStage *pixel) = 0; virtual StreamBuffer *newStreamBuffer(BufferType type, size_t size) = 0; - virtual void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) = 0; + virtual void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) = 0; virtual void initCapabilities() = 0; virtual void getAPIStats(int &shaderswitches) const = 0; void createQuadIndexBuffer(); - Canvas *getTemporaryCanvas(PixelFormat format, int w, int h, int samples); + Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples); void restoreState(const DisplayState &s); void restoreStateChecked(const DisplayState &s); @@ -1009,9 +996,9 @@ protected: std::vector states; std::vector stackTypeStack; - std::vector temporaryCanvases; + std::vector temporaryTextures; - int canvasSwitchCount; + int renderTargetSwitchCount; int drawCalls; int drawCallsBatched; @@ -1022,7 +1009,7 @@ protected: Deprecations deprecations; static const size_t MAX_USER_STACK_DEPTH = 128; - static const int MAX_TEMPORARY_CANVAS_UNUSED_FRAMES = 16; + static const int MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES = 16; private: diff --git a/src/modules/graphics/Image.cpp b/src/modules/graphics/Image.cpp deleted file mode 100644 index 2f155832a..000000000 --- a/src/modules/graphics/Image.cpp +++ /dev/null @@ -1,399 +0,0 @@ -/** - * Copyright (c) 2006-2020 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 "Image.h" -#include "Graphics.h" - -// C++ -#include - -namespace love -{ -namespace graphics -{ - -love::Type Image::type("Image", &Texture::type); - -int Image::imageCount = 0; - -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) - , usingDefaultTexture(false) -{ - if (validatedata && data.validate() == MIPMAPS_DATA) - mipmapsType = MIPMAPS_DATA; -} - -Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings) - : Image(Slices(textype), settings, false) -{ - if (isPixelFormatCompressed(format)) - throw love::Exception("This constructor is only supported for non-compressed pixel formats."); - - if (textype == TEXTURE_2D_ARRAY) - layers = slices; - else if (textype == TEXTURE_VOLUME) - depth = slices; - - init(format, width, height, settings); -} - -Image::Image(const Slices &slices, const Settings &settings) - : Image(slices, settings, true) -{ - if (texType == TEXTURE_2D_ARRAY) - this->layers = data.getSliceCount(); - else if (texType == TEXTURE_VOLUME) - this->depth = data.getSliceCount(); - - love::image::ImageDataBase *slice = data.get(0, 0); - init(slice->getFormat(), slice->getWidth(), slice->getHeight(), settings); -} - -Image::~Image() -{ - --imageCount; -} - -void Image::init(PixelFormat fmt, int w, int h, const Settings &settings) -{ - Graphics *gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr && !gfx->isImageFormatSupported(fmt, sRGB)) - { - const char *str; - if (love::getConstant(fmt, str)) - { - throw love::Exception("Cannot create image: " - "%s%s images are not supported on this system.", sRGB ? "sRGB " : "", str); - } - else - throw love::Exception("cannot create image: format is not supported on this system."); - } - - pixelWidth = w; - pixelHeight = h; - - width = (int) (pixelWidth / settings.dpiScale + 0.5); - height = (int) (pixelHeight / settings.dpiScale + 0.5); - - format = fmt; - - if (isCompressed() && mipmapsType == MIPMAPS_GENERATED) - mipmapsType = MIPMAPS_NONE; - - mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getTotalMipmapCount(w, h, depth); - - if (mipmapCount > 1) - filter.mipmap = defaultMipmapFilter; - - initQuad(); - - ++imageCount; -} - -void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y) -{ - love::image::ImageData *id = dynamic_cast(d); - - love::thread::EmptyLock lock; - if (id != nullptr) - lock.setLock(id->getMutex()); - - Rect rect = {x, y, d->getWidth(), d->getHeight()}; - uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect); -} - -void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps) -{ - // No effect if the texture hasn't been created yet. - if (getHandle() == 0 || usingDefaultTexture) - return; - - 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 %d.", mipmap + 1); - - if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6) - || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap)) - || (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount())) - { - throw love::Exception("Invalid image slice index %d.", slice + 1); - } - - Rect rect = {x, y, d->getWidth(), d->getHeight()}; - - int mipw = getPixelWidth(mipmap); - int miph = getPixelHeight(mipmap); - - if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0 - || (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph) - { - throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d Image.", rect.x, rect.y, rect.w, rect.h, mipw, miph); - } - - love::image::ImageDataBase *oldd = data.get(slice, mipmap); - - if (oldd == nullptr) - throw love::Exception("Image does not store ImageData!"); - - Rect currect = {0, 0, oldd->getWidth(), oldd->getHeight()}; - - // We can only replace the internal Data (used when reloading due to setMode) - // if the dimensions match. We also don't currently support partial updates - // of compressed textures. - if (rect == currect) - data.set(slice, mipmap, d); - else if (isPixelFormatCompressed(d->getFormat())) - throw love::Exception("Compressed textures only support replacing the entire Image."); - - Graphics::flushStreamDrawsGlobal(); - - uploadImageData(d, mipmap, slice, x, y); - - if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1) - generateMipmaps(); -} - -void Image::replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps) -{ - Graphics::flushStreamDrawsGlobal(); - - uploadByteData(format, data, size, mipmap, slice, rect); - - if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1) - generateMipmaps(); -} - -bool Image::isCompressed() const -{ - return isPixelFormatCompressed(format); -} - -bool Image::isFormatLinear() const -{ - return isGammaCorrect() && !sRGB; -} - -Image::MipmapsType Image::getMipmapsType() const -{ - return mipmapsType; -} - -Image::Slices::Slices(TextureType textype) - : textureType(textype) -{ -} - -void Image::Slices::clear() -{ - data.clear(); -} - -void Image::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d) -{ - 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); - } -} - -love::image::ImageDataBase *Image::Slices::get(int slice, int mipmap) const -{ - if (slice < 0 || slice >= getSliceCount(mipmap)) - return nullptr; - - 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(); - int depth = textureType == TEXTURE_VOLUME ? slicecount : 1; - PixelFormat format = firstdata->getFormat(); - - int expectedmips = Texture::getTotalMipmapCount(w, h, depth); - - 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; -} - -bool Image::getConstant(const char *in, SettingType &out) -{ - return settingTypes.find(in, out); -} - -bool Image::getConstant(SettingType in, const char *&out) -{ - return settingTypes.find(in, out); -} - -const char *Image::getConstant(SettingType in) -{ - const char *name = nullptr; - getConstant(in, name); - return name; -} - -std::vector Image::getConstants(SettingType) -{ - return settingTypes.getNames(); -} - -StringMap::Entry Image::settingTypeEntries[] = -{ - { "mipmaps", SETTING_MIPMAPS }, - { "linear", SETTING_LINEAR }, - { "dpiscale", SETTING_DPI_SCALE }, -}; - -StringMap Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries)); - -} // graphics -} // love diff --git a/src/modules/graphics/Image.h b/src/modules/graphics/Image.h deleted file mode 100644 index 9cb070137..000000000 --- a/src/modules/graphics/Image.h +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright (c) 2006-2020 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. - **/ - -#pragma once - -// LOVE -#include "common/config.h" -#include "common/StringMap.h" -#include "common/math.h" -#include "image/ImageData.h" -#include "image/CompressedImageData.h" -#include "Texture.h" - -namespace love -{ -namespace graphics -{ - -class Image : public Texture -{ -public: - - static love::Type type; - - enum MipmapsType - { - MIPMAPS_NONE, - MIPMAPS_DATA, - MIPMAPS_GENERATED, - }; - - enum SettingType - { - SETTING_MIPMAPS, - SETTING_LINEAR, - SETTING_DPI_SCALE, - SETTING_MAX_ENUM - }; - - struct Settings - { - bool mipmaps = false; - bool linear = false; - float dpiScale = 1.0f; - }; - - 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(); - - void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps); - void replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps); - - bool isFormatLinear() const; - bool isCompressed() const; - MipmapsType getMipmapsType() const; - - static int imageCount; - - static bool getConstant(const char *in, SettingType &out); - static bool getConstant(SettingType in, const char *&out); - static const char *getConstant(SettingType in); - static std::vector getConstants(SettingType); - -protected: - - Image(const Slices &data, const Settings &settings); - Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings); - - void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y); - virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) = 0; - - virtual void generateMipmaps() = 0; - - // The settings used to initialize this Image. - Settings settings; - - Slices data; - - MipmapsType mipmapsType; - 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; - -private: - - Image(const Slices &data, const Settings &settings, bool validatedata); - - void init(PixelFormat fmt, int w, int h, const Settings &settings); - - static StringMap::Entry settingTypeEntries[]; - static StringMap settingTypes; - -}; // Image - -} // graphics -} // love diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index 985fe7f05..ffd314fcb 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -125,7 +125,7 @@ void Shader::checkMainTexture(Texture *tex) const if (!tex->isReadable()) throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader."); - checkMainTextureType(tex->getTextureType(), tex->getDepthSampleMode().hasValue); + checkMainTextureType(tex->getTextureType(), tex->getSamplerState().depthSampleMode.hasValue); } bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err) diff --git a/src/modules/graphics/SpriteBatch.cpp b/src/modules/graphics/SpriteBatch.cpp index 2b7348aed..ce264b019 100644 --- a/src/modules/graphics/SpriteBatch.cpp +++ b/src/modules/graphics/SpriteBatch.cpp @@ -176,7 +176,7 @@ void SpriteBatch::flush() void SpriteBatch::setTexture(Texture *newtexture) { if (texture->getTextureType() != newtexture->getTextureType()) - throw love::Exception("Texture must have the same texture type as the SpriteBatch's previous texture."); + throw love::Exception("Texture must have the same type as the SpriteBatch's previous texture."); texture.set(newtexture); } diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index cbbbf0b75..b8ec94e6e 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -33,42 +33,264 @@ namespace love namespace graphics { -love::Type Texture::type("Texture", &Drawable::type); +uint64 SamplerState::toKey() const +{ + union { float f; uint32 i; } conv; + conv.f = lodBias; -Texture::Filter Texture::defaultFilter; -Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR; -float Texture::defaultMipmapSharpness = 0.0f; + return (minFilter << 0) | (magFilter << 1) | (mipmapFilter << 2) + | (wrapU << 4) | (wrapV << 7) | (wrapW << 10) + | (maxAnisotropy << 12) | (minLod << 16) | (maxLod << 20) + | (depthSampleMode.hasValue << 24) | (depthSampleMode.value << 25) + | ((uint64)conv.i << 32); +} + +SamplerState SamplerState::fromKey(uint64 key) +{ + const uint32 BITS_1 = 0x1; + const uint32 BITS_2 = 0x3; + const uint32 BITS_3 = 0x7; + const uint32 BITS_4 = 0xF; + + SamplerState s; + + s.minFilter = (FilterMode) ((key >> 0) & BITS_1); + s.magFilter = (FilterMode) ((key >> 1) & BITS_1); + s.mipmapFilter = (MipmapFilterMode) ((key >> 2) & BITS_2); + + s.wrapU = (WrapMode) ((key >> 4 ) & BITS_3); + s.wrapV = (WrapMode) ((key >> 7 ) & BITS_3); + s.wrapW = (WrapMode) ((key >> 10) & BITS_3); + + s.maxAnisotropy = (key >> 12) & BITS_4; + + s.minLod = (key >> 16) & BITS_4; + s.maxLod = (key >> 20) & BITS_4; + + s.depthSampleMode.hasValue = ((key >> 24) & BITS_1) != 0; + s.depthSampleMode.value = (CompareMode) ((key >> 25) & BITS_4); + + union { float f; uint32 i; } conv; + conv.i = (uint32) (key >> 32); + s.lodBias = conv.f; + + return s; +} + +bool SamplerState::isClampZeroOrOne(WrapMode w) +{ + return w == WRAP_CLAMP_ONE || w == WRAP_CLAMP_ZERO; +} + +static StringMap::Entry filterModeEntries[] = +{ + { "linear", SamplerState::FILTER_LINEAR }, + { "nearest", SamplerState::FILTER_NEAREST }, +}; + +static StringMap filterModes(filterModeEntries, sizeof(filterModeEntries)); + +static StringMap::Entry mipmapFilterModeEntries[] = +{ + { "none", SamplerState::MIPMAP_FILTER_NONE }, + { "linear", SamplerState::MIPMAP_FILTER_LINEAR }, + { "nearest", SamplerState::MIPMAP_FILTER_NEAREST }, +}; + +static StringMap mipmapFilterModes(mipmapFilterModeEntries, sizeof(mipmapFilterModeEntries)); + +static StringMap::Entry wrapModeEntries[] = +{ + { "clamp", SamplerState::WRAP_CLAMP }, + { "clampzero", SamplerState::WRAP_CLAMP_ZERO }, + { "clampone", SamplerState::WRAP_CLAMP_ONE }, + { "repeat", SamplerState::WRAP_REPEAT }, + { "mirroredrepeat", SamplerState::WRAP_MIRRORED_REPEAT }, +}; + +static StringMap wrapModes(wrapModeEntries, sizeof(wrapModeEntries)); + +bool SamplerState::getConstant(const char *in, FilterMode &out) +{ + return filterModes.find(in, out); +} + +bool SamplerState::getConstant(FilterMode in, const char *&out) +{ + return filterModes.find(in, out); +} + +std::vector SamplerState::getConstants(FilterMode) +{ + return filterModes.getNames(); +} + +bool SamplerState::getConstant(const char *in, MipmapFilterMode &out) +{ + return mipmapFilterModes.find(in, out); +} + +bool SamplerState::getConstant(MipmapFilterMode in, const char *&out) +{ + return mipmapFilterModes.find(in, out); +} + +std::vector SamplerState::getConstants(MipmapFilterMode) +{ + return mipmapFilterModes.getNames(); +} + +bool SamplerState::getConstant(const char *in, WrapMode &out) +{ + return wrapModes.find(in, out); +} + +bool SamplerState::getConstant(WrapMode in, const char *&out) +{ + return wrapModes.find(in, out); +} + +std::vector SamplerState::getConstants(WrapMode) +{ + return wrapModes.getNames(); +} + +love::Type Texture::type("Texture", &Drawable::type); +int Texture::textureCount = 0; int64 Texture::totalGraphicsMemory = 0; -Texture::Texture(TextureType texType) - : texType(texType) - , format(PIXELFORMAT_UNKNOWN) +Texture::Texture(const Settings &settings, const Slices *slices) + : texType(settings.type) + , format(settings.format) + , renderTarget(settings.renderTarget) , readable(true) - , width(0) - , height(0) - , depth(1) - , layers(1) + , mipmapsMode(settings.mipmaps) + , sRGB(isGammaCorrect() && !settings.linear) + , width(settings.width) + , height(settings.height) + , depth(settings.type == TEXTURE_VOLUME ? settings.layers : 1) + , layers(settings.type == TEXTURE_2D_ARRAY ? settings.layers : 1) , mipmapCount(1) , pixelWidth(0) , pixelHeight(0) - , filter(defaultFilter) - , wrap() - , mipmapSharpness(defaultMipmapSharpness) + , requestedMSAA(settings.msaa) + , samplerState() , graphicsMemorySize(0) + , usingDefaultTexture(false) { + auto gfx = Module::getInstance(Module::M_GRAPHICS); + + if (slices != nullptr && slices->getMipmapCount() > 0 && slices->getSliceCount() > 0) + { + texType = slices->getTextureType(); + + if (requestedMSAA > 1) + throw love::Exception("MSAA textures cannot be created from image data."); + + int dataMipmaps = 1; + if (slices->validate() && slices->getMipmapCount() > 1) + dataMipmaps = slices->getMipmapCount(); + + love::image::ImageDataBase *slice = slices->get(0, 0); + + format = slice->getFormat(); + + pixelWidth = slice->getWidth(); + pixelHeight = slice->getHeight(); + + if (texType == TEXTURE_2D_ARRAY) + layers = slices->getSliceCount(); + else if (texType == TEXTURE_VOLUME) + depth = slices->getSliceCount(); + + width = (int) (pixelWidth / settings.dpiScale + 0.5); + height = (int) (pixelHeight / settings.dpiScale + 0.5); + + if (isCompressed() && dataMipmaps <= 1) + mipmapsMode = MIPMAPS_NONE; + } + else + { + if (isCompressed()) + throw love::Exception("Compressed textures must be created with initial data."); + + pixelWidth = (int) ((width * settings.dpiScale) + 0.5); + pixelHeight = (int) ((height * settings.dpiScale) + 0.5); + } + + if (settings.readable.hasValue) + readable = settings.readable.value; + else + readable = !renderTarget || !isPixelFormatDepthStencil(format); + + format = gfx->getSizedFormat(format, renderTarget, readable, sRGB); + + if (mipmapsMode == MIPMAPS_AUTO && isCompressed()) + mipmapsMode = MIPMAPS_MANUAL; + + if (mipmapsMode != MIPMAPS_NONE) + mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth); + + if (pixelWidth <= 0 || pixelHeight <= 0 || layers <= 0 || depth <= 0) + throw love::Exception("Texture dimensions must be greater than 0."); + + if (texType != TEXTURE_2D && requestedMSAA > 1) + throw love::Exception("MSAA is only supported for textures with the 2D texture type."); + + if (!renderTarget && requestedMSAA > 1) + throw love::Exception("MSAA is only supported with render target textures."); + + if (readable && isPixelFormatDepthStencil(format) && settings.msaa > 1) + throw love::Exception("Readable depth/stencil textures with MSAA are not currently supported."); + + if ((!readable || settings.msaa > 1) && mipmapsMode != MIPMAPS_NONE) + throw love::Exception("Non-readable and MSAA textures cannot have mipmaps."); + + if (!readable && texType != TEXTURE_2D) + throw love::Exception("Non-readable pixel formats are only supported for 2D texture types."); + + if (isCompressed() && renderTarget) + throw love::Exception("Compressed textures cannot be render targets."); + + if (!gfx->isPixelFormatSupported(format, renderTarget, readable, sRGB)) + { + const char *fstr = "unknown"; + love::getConstant(format, fstr); + + const char *readablestr = ""; + if (readable != !isPixelFormatDepthStencil(format)) + readablestr = readable ? " readable" : " non-readable"; + + const char *rtstr = ""; + if (renderTarget) + rtstr = " as a render target"; + + throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr); + } + + if (!gfx->getCapabilities().textureTypes[texType]) + { + const char *textypestr = "unknown"; + Texture::getConstant(texType, textypestr); + throw love::Exception("%s textures are not supported on this system.", textypestr); + } + + validateDimensions(renderTarget || !readable); + + samplerState = gfx->getDefaultSamplerState(); + + Quad::Viewport v = {0, 0, (double) width, (double) height}; + quad.set(new Quad(v, width, height), Acquire::NORETAIN); + + ++textureCount; } Texture::~Texture() { + --textureCount; setGraphicsMemorySize(0); } -void Texture::initQuad() -{ - Quad::Viewport v = {0, 0, (double) width, (double) height}; - quad.set(new Quad(v, width, height), Acquire::NORETAIN); -} - void Texture::setGraphicsMemorySize(int64 bytes) { totalGraphicsMemory = std::max(totalGraphicsMemory - graphicsMemorySize, (int64) 0); @@ -78,38 +300,6 @@ void Texture::setGraphicsMemorySize(int64 bytes) totalGraphicsMemory += bytes; } -TextureType Texture::getTextureType() const -{ - return texType; -} - -PixelFormat Texture::getPixelFormat() const -{ - return format; -} - -bool Texture::isReadable() const -{ - return readable; -} - -bool Texture::isValidSlice(int slice) const -{ - if (slice < 0) - return false; - - if (texType == TEXTURE_CUBE) - return slice < 6; - else if (texType == TEXTURE_VOLUME) - return slice < depth; - else if (texType == TEXTURE_2D_ARRAY) - return slice < layers; - else if (slice > 0) - return false; - - return true; -} - void Texture::draw(Graphics *gfx, const Matrix4 &m) { draw(gfx, quad, m); @@ -122,6 +312,9 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform) if (!readable) throw love::Exception("Textures with non-readable formats cannot be drawn."); + if (renderTarget && gfx->isRenderTargetActive(this)) + throw love::Exception("Cannot render a Texture to itself."); + if (texType == TEXTURE_2D_ARRAY) { drawLayer(gfx, q->getLayer(), q, localTransform); @@ -172,8 +365,11 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m) if (!readable) throw love::Exception("Textures with non-readable formats cannot be drawn."); + if (renderTarget && gfx->isRenderTargetActive(this, layer)) + throw love::Exception("Cannot render a Texture to itself."); + if (texType != TEXTURE_2D_ARRAY) - throw love::Exception("drawLayer can only be used with Array Textures!"); + throw love::Exception("drawLayer can only be used with Array Textures."); if (layer < 0 || layer >= layers) throw love::Exception("Invalid layer: %d (Texture has %d layers)", layer + 1, layers); @@ -212,6 +408,176 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m) } } +void Texture::uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y) +{ + love::image::ImageData *id = dynamic_cast(d); + + love::thread::EmptyLock lock; + if (id != nullptr) + lock.setLock(id->getMutex()); + + Rect rect = {x, y, d->getWidth(), d->getHeight()}; + uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect, d); +} + +void Texture::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps) +{ + if (!isReadable()) + throw love::Exception("replacePixels can only be called on readable Textures."); + + if (getMSAA() > 1) + throw love::Exception("replacePixels cannot be called on a MSAA Texture."); + + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr && gfx->isRenderTargetActive(this)) + throw love::Exception("replacePixels cannot be called on this Texture while it's an active render target."); + + // No effect if the texture hasn't been created yet. + if (getHandle() == 0 || usingDefaultTexture) + return; + + if (d->getFormat() != getPixelFormat()) + throw love::Exception("Pixel formats must match."); + + if (mipmap < 0 || mipmap >= getMipmapCount()) + throw love::Exception("Invalid texture mipmap index %d.", mipmap + 1); + + if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6) + || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap)) + || (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount())) + { + throw love::Exception("Invalid texture slice index %d.", slice + 1); + } + + Rect rect = {x, y, d->getWidth(), d->getHeight()}; + + int mipw = getPixelWidth(mipmap); + int miph = getPixelHeight(mipmap); + + if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0 + || (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph) + { + throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d Texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph); + } + + // We don't currently support partial updates of compressed textures. + if (isPixelFormatCompressed(d->getFormat()) && (rect.x != 0 || rect.y != 0 || rect.w != mipw || rect.h != miph)) + throw love::Exception("Compressed textures only support replacing the entire Texture."); + + Graphics::flushStreamDrawsGlobal(); + + uploadImageData(d, mipmap, slice, x, y); + + if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1) + generateMipmaps(); +} + +void Texture::replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps) +{ + if (!isReadable() || getMSAA() > 1) + return; + + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr && gfx->isRenderTargetActive(this)) + return; + + Graphics::flushStreamDrawsGlobal(); + + uploadByteData(format, data, size, mipmap, slice, rect, nullptr); + + if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1) + generateMipmaps(); +} + +love::image::ImageData *Texture::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r) +{ + if (!isReadable()) + throw love::Exception("Texture:newImageData cannot be called on non-readable Textures."); + + if (!isRenderTarget()) + throw love::Exception("Texture:newImageData can only be called on render target Textures."); + + if (isPixelFormatDepthStencil(getPixelFormat())) + throw love::Exception("Texture:newImageData cannot be called on Textures with depth/stencil pixel formats."); + + if (r.x < 0 || r.y < 0 || r.w <= 0 || r.h <= 0 || (r.x + r.w) > getPixelWidth(mipmap) || (r.y + r.h) > getPixelHeight(mipmap)) + throw love::Exception("Invalid rectangle dimensions."); + + if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap)) + || (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->isRenderTargetActive(this)) + throw love::Exception("Texture:newImageData cannot be called while that Texture is an active render target."); + + PixelFormat dataformat = getLinearPixelFormat(getPixelFormat()); + + if (!image::ImageData::validPixelFormat(dataformat)) + { + const char *formatname = "unknown"; + love::getConstant(dataformat, formatname); + throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname); + } + + return module->newImageData(r.w, r.h, dataformat); +} + +TextureType Texture::getTextureType() const +{ + return texType; +} + +PixelFormat Texture::getPixelFormat() const +{ + return format; +} + +Texture::MipmapsMode Texture::getMipmapsMode() const +{ + return mipmapsMode; +} + +bool Texture::isRenderTarget() const +{ + return renderTarget; +} + +bool Texture::isReadable() const +{ + return readable; +} + +bool Texture::isCompressed() const +{ + return isPixelFormatCompressed(format); +} + +bool Texture::isFormatLinear() const +{ + return isGammaCorrect() && !sRGB && format != PIXELFORMAT_sRGBA8_UNORM; +} + +bool Texture::isValidSlice(int slice) const +{ + if (slice < 0) + return false; + + if (texType == TEXTURE_CUBE) + return slice < 6; + else if (texType == TEXTURE_VOLUME) + return slice < depth; + else if (texType == TEXTURE_2D_ARRAY) + return slice < layers; + else if (slice > 0) + return false; + + return true; +} + int Texture::getWidth(int mip) const { return std::max(width >> mip, 1); @@ -252,45 +618,33 @@ float Texture::getDPIScale() const return (float) pixelHeight / (float) height; } -void Texture::setFilter(const Filter &f) +int Texture::getRequestedMSAA() const { - if (!validateFilter(f, getMipmapCount() > 1)) - { - if (f.mipmap != FILTER_NONE && getMipmapCount() == 1) - throw love::Exception("Non-mipmapped texture cannot have mipmap filtering."); - else - throw love::Exception("Invalid texture filter."); - } + return requestedMSAA; +} + +void Texture::setSamplerState(const SamplerState &s) +{ + if (!readable) + return; + + if (s.depthSampleMode.hasValue && !isPixelFormatDepth(format)) + throw love::Exception("Only depth textures can have a depth sample compare mode."); Graphics::flushStreamDrawsGlobal(); - filter = f; + samplerState = s; + + if (samplerState.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && getMipmapCount() == 1) + samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + + if (texType == TEXTURE_CUBE) + samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP; } -const Texture::Filter &Texture::getFilter() const +const SamplerState &Texture::getSamplerState() const { - return filter; -} - -const Texture::Wrap &Texture::getWrap() const -{ - return wrap; -} - -float Texture::getMipmapSharpness() const -{ - return mipmapSharpness; -} - -void Texture::setDepthSampleMode(Optional mode) -{ - if (mode.hasValue && (!readable || !isPixelFormatDepthStencil(format))) - throw love::Exception("Only readable depth textures can have a depth sample compare mode."); -} - -Optional Texture::getDepthSampleMode() const -{ - return depthCompareMode; + return samplerState; } Quad *Texture::getQuad() const @@ -298,23 +652,6 @@ Quad *Texture::getQuad() const return quad; } -bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed) -{ - if (!mipmapsAllowed && f.mipmap != FILTER_NONE) - return false; - - if (f.mag != FILTER_LINEAR && f.mag != FILTER_NEAREST) - return false; - - if (f.min != FILTER_LINEAR && f.min != FILTER_NEAREST) - return false; - - if (f.mipmap != FILTER_LINEAR && f.mipmap != FILTER_NEAREST && f.mipmap != FILTER_NONE) - return false; - - return true; -} - int Texture::getTotalMipmapCount(int w, int h) { return (int) log2(std::max(w, h)) + 1; @@ -382,6 +719,201 @@ bool Texture::validateDimensions(bool throwException) const return success; } +Texture::Slices::Slices(TextureType textype) + : textureType(textype) +{ +} + +void Texture::Slices::clear() +{ + data.clear(); +} + +void Texture::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d) +{ + 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); + } +} + +love::image::ImageDataBase *Texture::Slices::get(int slice, int mipmap) const +{ + if (slice < 0 || slice >= getSliceCount(mipmap)) + return nullptr; + + if (mipmap < 0 || mipmap >= getMipmapCount(slice)) + return nullptr; + + if (textureType == TEXTURE_VOLUME) + return data[mipmap][slice].get(); + else + return data[slice][mipmap].get(); +} + +void Texture::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 Texture::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 Texture::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(); + } +} + +bool Texture::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(); + int depth = textureType == TEXTURE_VOLUME ? slicecount : 1; + PixelFormat format = firstdata->getFormat(); + + int expectedmips = Texture::getTotalMipmapCount(w, h, depth); + + if (mipcount != expectedmips && mipcount != 1) + throw love::Exception("Texture does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount); + + if (textureType == TEXTURE_CUBE && w != h) + throw love::Exception("Cube textures 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 texture 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 texture 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); + } + + return true; +} + +static StringMap::Entry texTypeEntries[] = +{ + { "2d", TEXTURE_2D }, + { "volume", TEXTURE_VOLUME }, + { "array", TEXTURE_2D_ARRAY }, + { "cube", TEXTURE_CUBE }, +}; + +static StringMap texTypes(texTypeEntries, sizeof(texTypeEntries)); + +static StringMap::Entry mipmapEntries[] = +{ + { "none", Texture::MIPMAPS_NONE }, + { "manual", Texture::MIPMAPS_MANUAL }, + { "auto", Texture::MIPMAPS_AUTO }, +}; + +static StringMap mipmapModes(mipmapEntries, sizeof(mipmapEntries)); + +static StringMap::Entry settingTypeEntries[] = +{ + { "width", Texture::SETTING_WIDTH }, + { "height", Texture::SETTING_HEIGHT }, + { "layers", Texture::SETTING_LAYERS }, + { "mipmaps", Texture::SETTING_MIPMAPS }, + { "format", Texture::SETTING_FORMAT }, + { "linear", Texture::SETTING_LINEAR }, + { "type", Texture::SETTING_TYPE }, + { "dpiscale", Texture::SETTING_DPI_SCALE }, + { "msaa", Texture::SETTING_MSAA }, + { "rendertarget", Texture::SETTING_RENDER_TARGET }, + { "readable", Texture::SETTING_READABLE }, +}; + +static StringMap settingTypes(settingTypeEntries, sizeof(settingTypeEntries)); + bool Texture::getConstant(const char *in, TextureType &out) { return texTypes.find(in, out); @@ -397,65 +929,42 @@ std::vector Texture::getConstants(TextureType) return texTypes.getNames(); } -bool Texture::getConstant(const char *in, FilterMode &out) +bool Texture::getConstant(const char *in, MipmapsMode &out) { - return filterModes.find(in, out); + return mipmapModes.find(in, out); } -bool Texture::getConstant(FilterMode in, const char *&out) +bool Texture::getConstant(MipmapsMode in, const char *&out) { - return filterModes.find(in, out); + return mipmapModes.find(in, out); } -std::vector Texture::getConstants(FilterMode) +std::vector Texture::getConstants(MipmapsMode) { - return filterModes.getNames(); + return mipmapModes.getNames(); } -bool Texture::getConstant(const char *in, WrapMode &out) +bool Texture::getConstant(const char *in, SettingType &out) { - return wrapModes.find(in, out); + return settingTypes.find(in, out); } -bool Texture::getConstant(WrapMode in, const char *&out) +bool Texture::getConstant(SettingType in, const char *&out) { - return wrapModes.find(in, out); + return settingTypes.find(in, out); } -std::vector Texture::getConstants(WrapMode) +const char *Texture::getConstant(SettingType in) { - return wrapModes.getNames(); + const char *name = nullptr; + getConstant(in, name); + return name; } -StringMap::Entry Texture::texTypeEntries[] = +std::vector Texture::getConstants(SettingType) { - { "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 }, - { "nearest", FILTER_NEAREST }, - { "none", FILTER_NONE }, -}; - -StringMap Texture::filterModes(Texture::filterModeEntries, sizeof(Texture::filterModeEntries)); - -StringMap::Entry Texture::wrapModeEntries[] = -{ - { "clamp", WRAP_CLAMP }, - { "clampzero", WRAP_CLAMP_ZERO }, - { "clampone", WRAP_CLAMP_ONE }, - { "repeat", WRAP_REPEAT }, - { "mirroredrepeat", WRAP_MIRRORED_REPEAT }, -}; - -StringMap Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries)); + return settingTypes.getNames(); +} } // graphics } // love diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index c308e8fc2..1f4e52671 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -33,6 +33,9 @@ #include "vertex.h" #include "renderstate.h" #include "Resource.h" +#include "image/ImageData.h" +#include "image/Image.h" +#include "image/CompressedImageData.h" // C #include @@ -53,16 +56,8 @@ enum TextureType TEXTURE_MAX_ENUM }; -/** - * Base class for 2D textures. All textures can be drawn with Quads, have a - * width and height, and have filter and wrap modes. - **/ -class Texture : public Drawable, public Resource +struct SamplerState { -public: - - static love::Type type; - enum WrapMode { WRAP_CLAMP, @@ -75,34 +70,138 @@ public: enum FilterMode { - FILTER_NONE, FILTER_LINEAR, FILTER_NEAREST, FILTER_MAX_ENUM }; - struct Filter + enum MipmapFilterMode { - FilterMode min = FILTER_LINEAR; - FilterMode mag = FILTER_LINEAR; - FilterMode mipmap = FILTER_NONE; - float anisotropy = 1.0f; + MIPMAP_FILTER_NONE, + MIPMAP_FILTER_LINEAR, + MIPMAP_FILTER_NEAREST, + MIPMAP_FILTER_MAX_ENUM }; - struct Wrap + FilterMode minFilter = FILTER_LINEAR; + FilterMode magFilter = FILTER_LINEAR; + MipmapFilterMode mipmapFilter = MIPMAP_FILTER_NONE; + + WrapMode wrapU = WRAP_CLAMP; + WrapMode wrapV = WRAP_CLAMP; + WrapMode wrapW = WRAP_CLAMP; + + float lodBias = 0.0f; + + uint8 maxAnisotropy = 1; + + uint8 minLod = 0; + uint8 maxLod = LOVE_UINT8_MAX; + + Optional depthSampleMode; + + uint64 toKey() const; + static SamplerState fromKey(uint64 key); + + static bool isClampZeroOrOne(WrapMode w); + + static bool getConstant(const char *in, FilterMode &out); + static bool getConstant(FilterMode in, const char *&out); + static std::vector getConstants(FilterMode); + + static bool getConstant(const char *in, MipmapFilterMode &out); + static bool getConstant(MipmapFilterMode in, const char *&out); + static std::vector getConstants(MipmapFilterMode); + + static bool getConstant(const char *in, WrapMode &out); + static bool getConstant(WrapMode in, const char *&out); + static std::vector getConstants(WrapMode); +}; + +/** + * Base class for 2D textures. All textures can be drawn with Quads, have a + * width and height, and have filter and wrap modes. + **/ +class Texture : public Drawable, public Resource +{ +public: + + static love::Type type; + static int textureCount; + + enum MipmapsMode { - WrapMode s = WRAP_CLAMP; - WrapMode t = WRAP_CLAMP; - WrapMode r = WRAP_CLAMP; + MIPMAPS_NONE, + MIPMAPS_MANUAL, + MIPMAPS_AUTO, + MIPMAPS_MAX_ENUM }; - static Filter defaultFilter; - static FilterMode defaultMipmapFilter; - static float defaultMipmapSharpness; + enum SettingType + { + SETTING_WIDTH, + SETTING_HEIGHT, + SETTING_LAYERS, + SETTING_MIPMAPS, + SETTING_FORMAT, + SETTING_LINEAR, + SETTING_TYPE, + SETTING_DPI_SCALE, + SETTING_MSAA, + SETTING_RENDER_TARGET, + SETTING_READABLE, + SETTING_MAX_ENUM + }; + + // Size and format will be overridden by ImageData when supplied. + struct Settings + { + int width = 1; + int height = 1; + int layers = 1; // depth for 3D textures + TextureType type = TEXTURE_2D; + MipmapsMode mipmaps = MIPMAPS_NONE; + PixelFormat format = PIXELFORMAT_NORMAL; + bool linear = false; + float dpiScale = 1.0f; + int msaa = 1; + bool renderTarget = false; + OptionalBool readable; + }; + + 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; + + bool 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 static int64 totalGraphicsMemory; - Texture(TextureType texType); + Texture(const Settings &settings, const Slices *slices); virtual ~Texture(); // Drawable. @@ -111,16 +210,30 @@ public: /** * Draws the texture using the specified transformation with a Quad applied. **/ - virtual void draw(Graphics *gfx, Quad *quad, const Matrix4 &m); + void draw(Graphics *gfx, Quad *quad, const Matrix4 &m); void drawLayer(Graphics *gfx, int layer, const Matrix4 &m); - virtual void drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m); + void drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m); + + void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps); + void replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps); + + virtual void generateMipmaps() = 0; + + virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect); + + virtual ptrdiff_t getRenderTargetHandle() const = 0; TextureType getTextureType() const; PixelFormat getPixelFormat() const; + MipmapsMode getMipmapsMode() const; + bool isRenderTarget() const; bool isReadable() const; + bool isCompressed() const; + bool isFormatLinear() const; + bool isValidSlice(int slice) const; int getWidth(int mip = 0) const; @@ -134,52 +247,49 @@ public: float getDPIScale() const; - virtual void setFilter(const Filter &f); - virtual const Filter &getFilter() const; + int getRequestedMSAA() const; + virtual int getMSAA() const = 0; - 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 void setDepthSampleMode(Optional mode = Optional()); - Optional getDepthSampleMode() const; + virtual void setSamplerState(const SamplerState &s); + const SamplerState &getSamplerState() const; Quad *getQuad() const; - static bool validateFilter(const Filter &f, bool mipmapsAllowed); - static int getTotalMipmapCount(int w, int h); static int getTotalMipmapCount(int w, int h, int d); - static bool isClampZeroOrOne(WrapMode w) { return w == WRAP_CLAMP_ZERO || w == WRAP_CLAMP_ONE; } - static bool getConstant(const char *in, TextureType &out); static bool getConstant(TextureType in, const char *&out); static std::vector getConstants(TextureType); - static bool getConstant(const char *in, FilterMode &out); - static bool getConstant(FilterMode in, const char *&out); - static std::vector getConstants(FilterMode); + static bool getConstant(const char *in, MipmapsMode &out); + static bool getConstant(MipmapsMode in, const char *&out); + static std::vector getConstants(MipmapsMode); - static bool getConstant(const char *in, WrapMode &out); - static bool getConstant(WrapMode in, const char *&out); - static std::vector getConstants(WrapMode); + static bool getConstant(const char *in, SettingType &out); + static bool getConstant(SettingType in, const char *&out); + static const char *getConstant(SettingType in); + static std::vector getConstants(SettingType); protected: - void initQuad(); void setGraphicsMemorySize(int64 size); + void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y); + virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd = nullptr) = 0; + bool validateDimensions(bool throwException) const; TextureType texType; PixelFormat format; + bool renderTarget; bool readable; + MipmapsMode mipmapsMode; + + bool sRGB; + int width; int height; @@ -190,27 +300,17 @@ protected: int pixelWidth; int pixelHeight; - Filter filter; - Wrap wrap; + int requestedMSAA; - float mipmapSharpness; - - Optional depthCompareMode; + SamplerState samplerState; StrongRef quad; int64 graphicsMemorySize; -private: - - static StringMap::Entry texTypeEntries[]; - static StringMap texTypes; - - static StringMap::Entry filterModeEntries[]; - static StringMap filterModes; - - static StringMap::Entry wrapModeEntries[]; - static StringMap wrapModes; + // True if the texture wasn't able to be properly created and it had to fall + // back to a default texture. + bool usingDefaultTexture; }; // Texture diff --git a/src/modules/graphics/Video.cpp b/src/modules/graphics/Video.cpp index 201670f6b..9f2df9c5b 100644 --- a/src/modules/graphics/Video.cpp +++ b/src/modules/graphics/Video.cpp @@ -35,9 +35,14 @@ Video::Video(Graphics *gfx, love::video::VideoStream *stream, float dpiscale) : stream(stream) , width(stream->getWidth() / dpiscale) , height(stream->getHeight() / dpiscale) - , filter(Texture::defaultFilter) + , samplerState() { - filter.mipmap = Texture::FILTER_NONE; + const SamplerState &defaultSampler = gfx->getDefaultSamplerState(); + samplerState.minFilter = defaultSampler.minFilter; + samplerState.magFilter = defaultSampler.magFilter; + samplerState.wrapU = defaultSampler.wrapU; + samplerState.wrapV = defaultSampler.wrapV; + samplerState.maxAnisotropy = defaultSampler.maxAnisotropy; stream->fillBackBuffer(); @@ -74,23 +79,24 @@ Video::Video(Graphics *gfx, love::video::VideoStream *stream, float dpiscale) const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane}; - Texture::Wrap wrap; // Clamp wrap mode. - Image::Settings settings; + Texture::Settings settings; for (int i = 0; i < 3; i++) { - Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8_UNORM, widths[i], heights[i], 1, settings); + settings.width = widths[i]; + settings.height = heights[i]; + settings.format = PIXELFORMAT_R8_UNORM; + Texture *tex = gfx->newTexture(settings, nullptr); - img->setFilter(filter); - img->setWrap(wrap); + tex->setSamplerState(samplerState); - size_t bpp = getPixelFormatSize(PIXELFORMAT_R8_UNORM); + size_t bpp = getPixelFormatBlockSize(PIXELFORMAT_R8_UNORM); size_t size = bpp * widths[i] * heights[i]; Rect rect = {0, 0, widths[i], heights[i]}; - img->replacePixels(data[i], size, 0, 0, rect, false); + tex->replacePixels(data[i], size, 0, 0, rect, false); - images[i].set(img, Acquire::NORETAIN); + textures[i].set(tex, Acquire::NORETAIN); } } @@ -140,7 +146,7 @@ void Video::draw(Graphics *gfx, const Matrix4 &m) } if (Shader::current != nullptr) - Shader::current->setVideoTextures(images[0], images[1], images[2]); + Shader::current->setVideoTextures(textures[0], textures[1], textures[2]); gfx->flushStreamDraws(); } @@ -161,11 +167,11 @@ void Video::update() for (int i = 0; i < 3; i++) { - size_t bpp = getPixelFormatSize(PIXELFORMAT_R8_UNORM); + size_t bpp = getPixelFormatBlockSize(PIXELFORMAT_R8_UNORM); size_t size = bpp * widths[i] * heights[i]; Rect rect = {0, 0, widths[i], heights[i]}; - images[i]->replacePixels(data[i], size, 0, 0, rect, false); + textures[i]->replacePixels(data[i], size, 0, 0, rect, false); } } } @@ -200,17 +206,21 @@ int Video::getPixelHeight() const return stream->getHeight(); } -void Video::setFilter(const Texture::Filter &f) +void Video::setSamplerState(const SamplerState &s) { - for (const auto &image : images) - image->setFilter(f); + samplerState.minFilter = s.minFilter; + samplerState.magFilter = s.magFilter; + samplerState.wrapU = s.wrapU; + samplerState.wrapV = s.wrapV; + samplerState.maxAnisotropy = s.maxAnisotropy; - filter = f; + for (const auto &texture : textures) + texture->setSamplerState(samplerState); } -const Texture::Filter &Video::getFilter() const +const SamplerState &Video::getSamplerState() const { - return filter; + return samplerState; } } // graphics diff --git a/src/modules/graphics/Video.h b/src/modules/graphics/Video.h index d307d3e79..871d8eb2e 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 "Image.h" +#include "Texture.h" #include "vertex.h" #include "video/VideoStream.h" #include "audio/Source.h" @@ -58,8 +58,8 @@ public: int getPixelWidth() const; int getPixelHeight() const; - void setFilter(const Texture::Filter &f); - const Texture::Filter &getFilter() const; + void setSamplerState(const SamplerState &s); + const SamplerState &getSamplerState() const; private: @@ -70,11 +70,11 @@ private: int width; int height; - Texture::Filter filter; + SamplerState samplerState; Vertex vertices[4]; - StrongRef images[3]; + StrongRef textures[3]; StrongRef source; }; // Video diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp deleted file mode 100644 index 8a91ab577..000000000 --- a/src/modules/graphics/opengl/Canvas.cpp +++ /dev/null @@ -1,626 +0,0 @@ -/** - * Copyright (c) 2006-2020 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 "Canvas.h" -#include "graphics/Graphics.h" -#include "Graphics.h" - -#include // For min/max - -namespace love -{ -namespace graphics -{ -namespace opengl -{ - -static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers, int nb_mips) -{ - // get currently bound fbo to reset to it later - GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); - - glGenFramebuffers(1, &framebuffer); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); - - if (texture != 0) - { - if (isPixelFormatDepthStencil(format) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0)) - { - // glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3. - GLenum none = GL_NONE; - if (GLAD_ES_VERSION_3_0) - glDrawBuffers(1, &none); - else - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - } - - bool unusedSRGB = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB); - - int faces = texType == TEXTURE_CUBE ? 6 : 1; - - // 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 mip = nb_mips - 1; mip >= 0; mip--) - { - int nlayers = layers; - if (texType == TEXTURE_VOLUME) - nlayers = std::max(layers >> mip, 1); - - for (int layer = nlayers - 1; layer >= 0; layer--) - { - for (int face = faces - 1; face >= 0; face--) - { - for (GLenum attachment : fmt.framebufferAttachments) - { - if (attachment == GL_NONE) - continue; - - gl.framebufferTexture(attachment, texType, texture, mip, layer, face); - } - - if (isPixelFormatDepthStencil(format)) - { - bool hadDepthWrites = gl.hasDepthWrites(); - if (!hadDepthWrites) // glDepthMask also affects glClear. - gl.setDepthWrites(true); - - gl.clearDepth(1.0); - glClearStencil(0); - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - if (!hadDepthWrites) - gl.setDepthWrites(hadDepthWrites); - } - else - { - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - } - } - } - } - } - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - return status; -} - -static bool createRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer) -{ - int reqsamples = samples; - bool unusedSRGB = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true, unusedSRGB); - - GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); - - // Temporary FBO used to clear the renderbuffer. - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); - - if (isPixelFormatDepthStencil(pixelformat) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0)) - { - // glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3. - GLenum none = GL_NONE; - if (GLAD_ES_VERSION_3_0) - glDrawBuffers(1, &none); - else - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - } - - glGenRenderbuffers(1, &buffer); - glBindRenderbuffer(GL_RENDERBUFFER, buffer); - - if (samples > 1) - glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height); - else - glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, width, height); - - for (GLenum attachment : fmt.framebufferAttachments) - { - if (attachment != GL_NONE) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffer); - } - - if (samples > 1) - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); - else - samples = 0; - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - if (status == GL_FRAMEBUFFER_COMPLETE && (reqsamples <= 1 || samples > 1)) - { - if (isPixelFormatDepthStencil(pixelformat)) - { - bool hadDepthWrites = gl.hasDepthWrites(); - if (!hadDepthWrites) // glDepthMask also affects glClear. - gl.setDepthWrites(true); - - gl.clearDepth(1.0); - glClearStencil(0); - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - if (!hadDepthWrites) - gl.setDepthWrites(hadDepthWrites); - } - else - { - // Initialize the buffer to transparent black. - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - } - } - else - { - glDeleteRenderbuffers(1, &buffer); - buffer = 0; - samples = 0; - } - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - gl.deleteFramebuffer(fbo); - - return status == GL_FRAMEBUFFER_COMPLETE; -} - -Canvas::Canvas(const Settings &settings) - : love::graphics::Canvas(settings) - , fbo(0) - , texture(0) - , renderbuffer(0) - , actualSamples(0) -{ - format = getSizedFormat(format); - - initQuad(); - loadVolatile(); - - if (status != GL_FRAMEBUFFER_COMPLETE) - throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(status)); -} - -Canvas::~Canvas() -{ - unloadVolatile(); -} - -bool Canvas::loadVolatile() -{ - if (texture != 0) - return true; - - OpenGL::TempDebugGroup debuggroup("Canvas load"); - - fbo = texture = 0; - renderbuffer = 0; - status = GL_FRAMEBUFFER_COMPLETE; - - // getMaxRenderbufferSamples will be 0 on systems that don't support - // multisampled renderbuffers / don't export FBO multisample extensions. - actualSamples = std::min(getRequestedMSAA(), gl.getMaxRenderbufferSamples()); - actualSamples = std::max(actualSamples, 0); - actualSamples = actualSamples == 1 ? 0 : actualSamples; - - if (isReadable()) - { - glGenTextures(1, &texture); - gl.bindTextureToUnit(this, 0, false); - - GLenum gltype = OpenGL::getGLTextureType(texType); - - if (GLAD_ANGLE_texture_usage) - glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); - - setFilter(filter); - setWrap(wrap); - setMipmapSharpness(mipmapSharpness); - setDepthSampleMode(depthCompareMode); - - while (glGetError() != GL_NO_ERROR) - /* Clear the error buffer. */; - - bool isSRGB = format == PIXELFORMAT_sRGBA8_UNORM; - if (!gl.rawTexStorage(texType, mipmapCount, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers)) - { - status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; - return false; - } - - if (glGetError() != GL_NO_ERROR) - { - gl.deleteTexture(texture); - texture = 0; - status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; - return false; - } - - // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. - status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers, mipmapCount); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - if (fbo != 0) - { - gl.deleteFramebuffer(fbo); - fbo = 0; - } - return false; - } - } - - if (!isReadable() || actualSamples > 0) - createRenderbuffer(pixelWidth, pixelHeight, actualSamples, format, renderbuffer); - - int64 memsize = getPixelFormatSize(format) * pixelWidth * pixelHeight; - if (getMipmapCount() > 1) - memsize *= 1.33334; - - if (actualSamples > 1 && isReadable()) - memsize += getPixelFormatSize(format) * pixelWidth * pixelHeight * actualSamples; - else if (actualSamples > 1) - memsize *= actualSamples; - - setGraphicsMemorySize(memsize); - - return true; -} - -void Canvas::unloadVolatile() -{ - if (fbo != 0 || renderbuffer != 0 || texture != 0) - { - // This is a bit ugly, but we need some way to destroy the cached FBO - // when this Canvas' texture is destroyed. - auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr) - gfx->cleanupCanvas(this); - } - - if (fbo != 0) - gl.deleteFramebuffer(fbo); - - if (renderbuffer != 0) - glDeleteRenderbuffers(1, &renderbuffer); - - if (texture != 0) - gl.deleteTexture(texture); - - fbo = 0; - renderbuffer = 0; - texture = 0; - - setGraphicsMemorySize(0); -} - -void Canvas::setFilter(const Texture::Filter &f) -{ - Texture::setFilter(f); - - if (!OpenGL::hasTextureFilteringSupport(getPixelFormat())) - { - filter.mag = filter.min = FILTER_NEAREST; - - if (filter.mipmap == FILTER_LINEAR) - filter.mipmap = FILTER_NEAREST; - } - - gl.bindTextureToUnit(this, 0, false); - gl.setTextureFilter(texType, filter); -} - -bool Canvas::setWrap(const Texture::Wrap &w) -{ - Graphics::flushStreamDrawsGlobal(); - - 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) || depth != nextP2(depth))) - { - forceclamp = true; - } - - if (forceclamp) - { - if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP) - success = false; - - wrap.s = wrap.t = wrap.r = WRAP_CLAMP; - } - - if (!gl.isClampZeroOneTextureWrapSupported()) - { - if (isClampZeroOrOne(wrap.s)) wrap.s = WRAP_CLAMP; - if (isClampZeroOrOne(wrap.t)) wrap.t = WRAP_CLAMP; - if (isClampZeroOrOne(wrap.r)) wrap.r = WRAP_CLAMP; - } - - gl.bindTextureToUnit(this, 0, false); - gl.setTextureWrap(texType, wrap); - - return success; -} - -bool Canvas::setMipmapSharpness(float sharpness) -{ - if (!gl.isSamplerLODBiasSupported()) - return false; - - Graphics::flushStreamDrawsGlobal(); - - float maxbias = gl.getMaxLODBias(); - if (maxbias > 0.01f) - maxbias -= 0.0f; - - mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias); - - gl.bindTextureToUnit(this, 0, false); - - // negative bias is sharper - glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness); - - return true; -} - -void Canvas::setDepthSampleMode(Optional mode) -{ - Texture::setDepthSampleMode(mode); - - bool supported = gl.isDepthCompareSampleSupported(); - - if (mode.hasValue) - { - if (!supported) - throw love::Exception("Depth comparison sampling in shaders is not supported on this system."); - - Graphics::flushStreamDrawsGlobal(); - - gl.bindTextureToUnit(texType, texture, 0, false); - GLenum gltextype = OpenGL::getGLTextureType(texType); - - // See the comment in renderstate.h - GLenum glmode = OpenGL::getGLCompareMode(getReversedCompareMode(mode.value)); - - glTexParameteri(gltextype, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - glTexParameteri(gltextype, GL_TEXTURE_COMPARE_FUNC, glmode); - - } - else if (isPixelFormatDepth(format) && supported) - { - Graphics::flushStreamDrawsGlobal(); - - gl.bindTextureToUnit(texType, texture, 0, false); - GLenum gltextype = OpenGL::getGLTextureType(texType); - - glTexParameteri(gltextype, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } - - depthCompareMode = mode; -} - -ptrdiff_t Canvas::getHandle() const -{ - return texture; -} - -love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r) -{ - love::image::ImageData *data = love::graphics::Canvas::newImageData(module, slice, mipmap, r); - - bool isSRGB = false; - OpenGL::TextureFormat fmt = gl.convertPixelFormat(data->getFormat(), false, isSRGB); - - GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO()); - - if (slice > 0 || mipmap > 0) - { - int layer = texType == TEXTURE_CUBE ? 0 : slice; - int face = texType == TEXTURE_CUBE ? slice : 0; - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face); - } - - glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data->getData()); - - if (slice > 0 || mipmap > 0) - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0); - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - - return data; -} - -void Canvas::generateMipmaps() -{ - if (getMipmapCount() == 1 || getMipmapMode() == MIPMAPS_NONE) - throw love::Exception("generateMipmaps can only be called on a Canvas which was created with mipmaps enabled."); - - gl.bindTextureToUnit(this, 0, false); - - GLenum gltextype = OpenGL::getGLTextureType(texType); - - if (gl.bugs.generateMipmapsRequiresTexture2DEnable) - glEnable(gltextype); - - glGenerateMipmap(gltextype); -} - -PixelFormat Canvas::getSizedFormat(PixelFormat format) -{ - switch (format) - { - case PIXELFORMAT_NORMAL: - if (isGammaCorrect()) - return PIXELFORMAT_sRGBA8_UNORM; - else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, true, true, false)) - // 32-bit render targets don't have guaranteed support on GLES2. - return PIXELFORMAT_RGBA4_UNORM; - else - return PIXELFORMAT_RGBA8_UNORM; - case PIXELFORMAT_HDR: - return PIXELFORMAT_RGBA16_FLOAT; - default: - return format; - } -} - -bool Canvas::isSupported() -{ - return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object; -} - -bool Canvas::isMultiFormatMultiCanvasSupported() -{ - return gl.getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object); -} - -Canvas::SupportedFormat Canvas::supportedFormats[] = {}; -Canvas::SupportedFormat Canvas::checkedFormats[] = {}; - -bool Canvas::isFormatSupported(PixelFormat format) -{ - return isFormatSupported(format, !isPixelFormatDepthStencil(format)); -} - -bool Canvas::isFormatSupported(PixelFormat format, bool readable) -{ - if (!isSupported()) - return false; - - const char *fstr = "?"; - love::getConstant(format, fstr); - - bool supported = true; - format = getSizedFormat(format); - - if (!OpenGL::isPixelFormatSupported(format, true, readable, false)) - return false; - - if (checkedFormats[format].get(readable)) - return supportedFormats[format].get(readable); - - // Even though we might have the necessary OpenGL version or extension, - // drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching - // a texture to a FBO whose format the driver doesn't like. So we should - // test with an actual FBO. - GLuint texture = 0; - GLuint renderbuffer = 0; - - // Avoid the test for depth/stencil formats - not every GL version - // guarantees support for depth/stencil-only render targets (which we would - // need for the test below to work), and we already do some finagling in - // convertPixelFormat to try to use the best-supported internal - // depth/stencil format for a particular driver. - if (isPixelFormatDepthStencil(format)) - { - checkedFormats[format].set(readable, true); - supportedFormats[format].set(readable, true); - return true; - } - - bool unusedSRGB = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, unusedSRGB); - - GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); - - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); - - // Make sure at least something is bound to a color attachment. I believe - // this is required on ES2 but I'm not positive. - if (isPixelFormatDepthStencil(format)) - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, 0, 0); - - if (readable) - { - glGenTextures(1, &texture); - gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); - - Texture::Filter f; - f.min = f.mag = Texture::FILTER_NEAREST; - gl.setTextureFilter(TEXTURE_2D, f); - - Texture::Wrap w; - gl.setTextureWrap(TEXTURE_2D, w); - - unusedSRGB = false; - gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 1, 1); - } - else - { - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); - } - - for (GLenum attachment : fmt.framebufferAttachments) - { - if (attachment == GL_NONE) - continue; - - if (readable) - gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); - else - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); - } - - supported = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - gl.deleteFramebuffer(fbo); - - if (texture != 0) - gl.deleteTexture(texture); - - if (renderbuffer != 0) - glDeleteRenderbuffers(1, &renderbuffer); - - // Cache the result so we don't do this for every isFormatSupported call. - checkedFormats[format].set(readable, true); - supportedFormats[format].set(readable, supported); - - return supported; -} - -void Canvas::resetFormatSupport() -{ - for (int i = 0; i < (int)PIXELFORMAT_MAX_ENUM; i++) - { - checkedFormats[i].readable = false; - checkedFormats[i].nonreadable = false; - } -} - -} // opengl -} // graphics -} // love diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h deleted file mode 100644 index 317d9eeea..000000000 --- a/src/modules/graphics/opengl/Canvas.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2006-2020 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. - **/ - -#ifndef LOVE_GRAPHICS_OPENGL_CANVAS_H -#define LOVE_GRAPHICS_OPENGL_CANVAS_H - -#include "common/config.h" -#include "common/Color.h" -#include "common/int.h" -#include "graphics/Canvas.h" -#include "graphics/Volatile.h" -#include "OpenGL.h" - -namespace love -{ -namespace graphics -{ -namespace opengl -{ - -class Canvas final : public love::graphics::Canvas, public Volatile -{ -public: - - Canvas(const Settings &settings); - virtual ~Canvas(); - - // Implements Volatile. - bool loadVolatile() override; - void unloadVolatile() override; - - // Implements Texture. - void setFilter(const Texture::Filter &f) override; - bool setWrap(const Texture::Wrap &w) override; - bool setMipmapSharpness(float sharpness) override; - void setDepthSampleMode(Optional mode) override; - ptrdiff_t getHandle() const override; - - love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect) override; - void generateMipmaps() override; - - int getMSAA() const override - { - return actualSamples; - } - - ptrdiff_t getRenderTargetHandle() const override - { - return renderbuffer != 0 ? renderbuffer : texture; - } - - inline GLuint getFBO() const - { - return fbo; - } - - static PixelFormat getSizedFormat(PixelFormat format); - static bool isSupported(); - static bool isMultiFormatMultiCanvasSupported(); - static bool isFormatSupported(PixelFormat format, bool readable); - static bool isFormatSupported(PixelFormat format); - static void resetFormatSupport(); - -private: - - struct SupportedFormat - { - bool readable = false; - bool nonreadable = false; - - bool get(bool getreadable) - { - return getreadable ? readable : nonreadable; - } - - void set(bool setreadable, bool val) - { - if (setreadable) - readable = val; - else - nonreadable = val; - } - }; - - GLuint fbo; - - GLuint texture; - GLuint renderbuffer; - - GLenum status; - - int actualSamples; - - static SupportedFormat supportedFormats[PIXELFORMAT_MAX_ENUM]; - static SupportedFormat checkedFormats[PIXELFORMAT_MAX_ENUM]; - -}; // Canvas - -} // opengl -} // graphics -} // love - -#endif // LOVE_GRAPHICS_OPENGL_CANVAS_H diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 11278fdad..c65354b01 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -107,9 +107,9 @@ love::graphics::Graphics *createInstance() Graphics::Graphics() : windowHasStencil(false) , mainVAO(0) + , supportedFormats() { gl = OpenGL(); - Canvas::resetFormatSupport(); auto window = getInstance(M_WINDOW); @@ -146,19 +146,9 @@ love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t return CreateStreamBuffer(type, size); } -love::graphics::Image *Graphics::newImage(const Image::Slices &data, const Image::Settings &settings) +love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, const Texture::Slices *data) { - return new Image(data, settings); -} - -love::graphics::Image *Graphics::newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) -{ - return new Image(textype, format, width, height, slices, settings); -} - -love::graphics::Canvas *Graphics::newCanvas(const Canvas::Settings &settings) -{ - return new Canvas(settings); + return new Texture(settings, data); } love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) @@ -183,7 +173,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh this->pixelWidth = pixelwidth; this->pixelHeight = pixelheight; - if (!isCanvasActive()) + if (!isRenderTargetActive()) { // Set the viewport to top-left corner. gl.setViewport({0, 0, pixelwidth, pixelheight}); @@ -250,7 +240,7 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b // 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) + || GLAD_ES_VERSION_3_0) { if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, isGammaCorrect()); @@ -328,11 +318,11 @@ void Graphics::unSetMode() for (const auto &pair : framebufferObjects) gl.deleteFramebuffer(pair.second); - for (auto temp : temporaryCanvases) - temp.canvas->release(); + for (auto temp : temporaryTextures) + temp.texture->release(); framebufferObjects.clear(); - temporaryCanvases.clear(); + temporaryTextures.clear(); if (mainVAO != 0) { @@ -525,24 +515,24 @@ void Graphics::setDebug(bool enable) ::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n"); } -void Graphics::setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) +void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) { const DisplayState &state = states.back(); - OpenGL::TempDebugGroup debuggroup("setCanvas"); + OpenGL::TempDebugGroup debuggroup("setRenderTargets"); flushStreamDraws(); endPass(); - bool iswindow = rts.getFirstTarget().canvas == nullptr; + bool iswindow = rts.getFirstTarget().texture == nullptr; vertex::Winding vertexwinding = state.winding; if (iswindow) { gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); - // The projection matrix is flipped compared to rendering to a canvas, due - // to OpenGL considering (0,0) bottom-left instead of top-left. + // The projection matrix is flipped compared to rendering to a texture, + // due to OpenGL considering (0,0) bottom-left instead of top-left. projectionMatrix = Matrix4::ortho(0.0, (float) w, (float) h, 0.0, -10.0f, 10.0f); } else @@ -551,7 +541,7 @@ void Graphics::setCanvasInternal(const RenderTargets &rts, int w, int h, int pix projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h, -10.0f, 10.0f); - // Flip front face winding when rendering to a canvas, since our + // Flip front face winding when rendering to a texture, since our // projection matrix is flipped. vertexwinding = vertexwinding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW; } @@ -565,18 +555,18 @@ void Graphics::setCanvasInternal(const RenderTargets &rts, int w, int h, int pix if (state.scissor) setScissor(state.scissorRect); - // Make sure the correct sRGB setting is used when drawing to the canvases. + // Make sure the correct sRGB setting is used when drawing to the textures. if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) { - if (hasSRGBcanvas != gl.isStateEnabled(OpenGL::ENABLE_FRAMEBUFFER_SRGB)) - gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, hasSRGBcanvas); + if (hasSRGBtexture != gl.isStateEnabled(OpenGL::ENABLE_FRAMEBUFFER_SRGB)) + gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, hasSRGBtexture); } } void Graphics::endPass() { auto &rts = states.back().renderTargets; - love::graphics::Canvas *depthstencil = rts.depthStencil.canvas.get(); + love::graphics::Texture *depthstencil = rts.depthStencil.texture.get(); // Discard the depth/stencil buffer if we're using an internal cached one. if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0) @@ -584,15 +574,15 @@ void Graphics::endPass() // Resolve MSAA buffers. MSAA is only supported for 2D render targets so we // don't have to worry about resolving to slices. - if (rts.colors.size() > 0 && rts.colors[0].canvas->getMSAA() > 1) + if (rts.colors.size() > 0 && rts.colors[0].texture->getMSAA() > 1) { int mip = rts.colors[0].mipmap; - int w = rts.colors[0].canvas->getPixelWidth(mip); - int h = rts.colors[0].canvas->getPixelHeight(mip); + int w = rts.colors[0].texture->getPixelWidth(mip); + int h = rts.colors[0].texture->getPixelHeight(mip); for (int i = 0; i < (int) rts.colors.size(); i++) { - Canvas *c = (Canvas *) rts.colors[i].canvas.get(); + Texture *c = (Texture *) rts.colors[i].texture.get(); if (!c->isReadable()) continue; @@ -610,7 +600,7 @@ void Graphics::endPass() if (depthstencil != nullptr && depthstencil->getMSAA() > 1 && depthstencil->isReadable()) { - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, ((Canvas *) depthstencil)->getFBO()); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, ((Texture *) depthstencil)->getFBO()); if (GLAD_APPLE_framebuffer_multisample) glResolveMultisampleFramebufferAPPLE(); @@ -635,12 +625,12 @@ void Graphics::endPass() for (const auto &rt : rts.colors) { - if (rt.canvas->getMipmapMode() == Canvas::MIPMAPS_AUTO && rt.mipmap == 0) - rt.canvas->generateMipmaps(); + if (rt.texture->getMipmapsMode() == Texture::MIPMAPS_AUTO && rt.mipmap == 0) + rt.texture->generateMipmaps(); } int dsmipmap = rts.depthStencil.mipmap; - if (depthstencil != nullptr && depthstencil->getMipmapMode() == Canvas::MIPMAPS_AUTO && dsmipmap == 0) + if (depthstencil != nullptr && depthstencil->getMipmapsMode() == Texture::MIPMAPS_AUTO && dsmipmap == 0) depthstencil->generateMipmaps(); } @@ -695,10 +685,10 @@ void Graphics::clear(const std::vector &colors, OptionalInt sten if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) return; - int ncolorcanvases = (int) states.back().renderTargets.colors.size(); + int ncolorRTs = (int) states.back().renderTargets.colors.size(); int ncolors = (int) colors.size(); - if (ncolors <= 1 && ncolorcanvases <= 1) + if (ncolors <= 1 && ncolorRTs <= 1) { clear(ncolors > 0 ? colors[0] : OptionalColorf(), stencil, depth); return; @@ -707,7 +697,7 @@ void Graphics::clear(const std::vector &colors, OptionalInt sten flushStreamDraws(); bool drawbuffersmodified = false; - ncolors = std::min(ncolors, ncolorcanvases); + ncolors = std::min(ncolors, ncolorRTs); for (int i = 0; i < ncolors; i++) { @@ -738,10 +728,10 @@ void Graphics::clear(const std::vector &colors, OptionalInt sten { GLenum bufs[MAX_COLOR_RENDER_TARGETS]; - for (int i = 0; i < ncolorcanvases; i++) + for (int i = 0; i < ncolorRTs; i++) bufs[i] = GL_COLOR_ATTACHMENT0 + i; - glDrawBuffers(ncolorcanvases, bufs); + glDrawBuffers(ncolorRTs, bufs); } GLbitfield flags = 0; @@ -799,7 +789,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (!isCanvasActive() && gl.getDefaultFBO() == 0) + if (!isRenderTargetActive() && gl.getDefaultFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); @@ -834,25 +824,28 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]); } -void Graphics::cleanupCanvas(Canvas *canvas) +void Graphics::cleanupRenderTexture(love::graphics::Texture *texture) { + if (!texture->isRenderTarget()) + return; + for (auto it = framebufferObjects.begin(); it != framebufferObjects.end(); /**/) { - bool hascanvas = false; + bool hastexture = false; const auto &rts = it->first; for (const RenderTarget &rt : rts.colors) { - if (rt.canvas == canvas) + if (rt.texture == texture) { - hascanvas = true; + hastexture = true; break; } } - hascanvas = hascanvas || rts.depthStencil.canvas == canvas; + hastexture = hastexture || rts.depthStencil.texture == texture; - if (hascanvas) + if (hastexture) { if (isCreated()) gl.deleteFramebuffer(it->second); @@ -873,8 +866,8 @@ void Graphics::bindCachedFBO(const RenderTargets &targets) } else { - int msaa = targets.getFirstTarget().canvas->getMSAA(); - bool hasDS = targets.depthStencil.canvas != nullptr; + int msaa = targets.getFirstTarget().texture->getMSAA(); + bool hasDS = targets.depthStencil.texture != nullptr; glGenFramebuffers(1, &fbo); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); @@ -882,11 +875,11 @@ void Graphics::bindCachedFBO(const RenderTargets &targets) int ncolortargets = 0; GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS]; - auto attachCanvas = [&](const RenderTarget &rt) + auto attachRT = [&](const RenderTarget &rt) { - bool renderbuffer = msaa > 1 || !rt.canvas->isReadable(); + bool renderbuffer = msaa > 1 || !rt.texture->isReadable(); bool srgb = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.canvas->getPixelFormat(), renderbuffer, srgb); + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.texture->getPixelFormat(), renderbuffer, srgb); if (fmt.framebufferAttachments[0] == GL_COLOR_ATTACHMENT0) { @@ -895,7 +888,7 @@ void Graphics::bindCachedFBO(const RenderTargets &targets) ncolortargets++; } - GLuint handle = (GLuint) rt.canvas->getRenderTargetHandle(); + GLuint handle = (GLuint) rt.texture->getRenderTargetHandle(); for (GLenum attachment : fmt.framebufferAttachments) { @@ -905,7 +898,7 @@ void Graphics::bindCachedFBO(const RenderTargets &targets) glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, handle); else { - TextureType textype = rt.canvas->getTextureType(); + TextureType textype = rt.texture->getTextureType(); int layer = textype == TEXTURE_CUBE ? 0 : rt.slice; int face = textype == TEXTURE_CUBE ? rt.slice : 0; @@ -917,10 +910,10 @@ void Graphics::bindCachedFBO(const RenderTargets &targets) }; for (const auto &rt : targets.colors) - attachCanvas(rt); + attachRT(rt); if (hasDS) - attachCanvas(targets.depthStencil); + attachRT(targets.depthStencil); if (ncolortargets > 1) glDrawBuffers(ncolortargets, drawbuffers); @@ -953,8 +946,8 @@ void Graphics::present(void *screenshotCallbackData) if (!isActive()) return; - if (isCanvasActive()) - throw love::Exception("present cannot be called while a Canvas is active."); + if (isRenderTargetActive()) + throw love::Exception("present cannot be called while a render target is active."); deprecations.draw(this); @@ -1072,20 +1065,20 @@ void Graphics::present(void *screenshotCallbackData) // Reset the per-frame stat counts. drawCalls = 0; gl.stats.shaderSwitches = 0; - canvasSwitchCount = 0; + renderTargetSwitchCount = 0; drawCallsBatched = 0; - // This assumes temporary canvases will only be used within a render pass. - for (int i = (int) temporaryCanvases.size() - 1; i >= 0; i--) + // This assumes temporary textures will only be used within a render pass. + for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--) { - if (temporaryCanvases[i].framesSinceUse >= MAX_TEMPORARY_CANVAS_UNUSED_FRAMES) + if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES) { - temporaryCanvases[i].canvas->release(); - temporaryCanvases[i] = temporaryCanvases.back(); - temporaryCanvases.pop_back(); + temporaryTextures[i].texture->release(); + temporaryTextures[i] = temporaryTextures.back(); + temporaryTextures.pop_back(); } else - temporaryCanvases[i].framesSinceUse++; + temporaryTextures[i].framesSinceUse++; } } @@ -1107,7 +1100,7 @@ void Graphics::setScissor(const Rect &rect) glrect.h = (int) (rect.h * dpiscale); // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor. - gl.setScissor(glrect, isCanvasActive()); + gl.setScissor(glrect, isRenderTargetActive()); state.scissor = true; state.scissorRect = rect; @@ -1127,12 +1120,12 @@ void Graphics::setScissor() void Graphics::drawToStencilBuffer(StencilAction action, int value) { const auto &rts = states.back().renderTargets; - love::graphics::Canvas *dscanvas = rts.depthStencil.canvas.get(); + love::graphics::Texture *dstexture = rts.depthStencil.texture.get(); - if (!isCanvasActive() && !windowHasStencil) + if (!isRenderTargetActive() && !windowHasStencil) throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer."); - else if (isCanvasActive() && (rts.temporaryRTFlags & TEMPORARY_RT_STENCIL) == 0 && (dscanvas == nullptr || !isPixelFormatStencil(dscanvas->getPixelFormat()))) - throw love::Exception("Drawing to the stencil buffer with a Canvas active requires either stencil=true or a custom stencil-type Canvas to be used, in setCanvas."); + else if (isRenderTargetActive() && (rts.temporaryRTFlags & TEMPORARY_RT_STENCIL) == 0 && (dstexture == nullptr || !isPixelFormatStencil(dstexture->getPixelFormat()))) + throw love::Exception("Drawing to the stencil buffer with a render target active requires either stencil=true or a custom stencil-type texture to be used, in setRenderTarget."); flushStreamDraws(); @@ -1260,7 +1253,7 @@ void Graphics::setFrontFaceWinding(vertex::Winding winding) state.winding = winding; - if (isCanvasActive()) + if (isRenderTargetActive()) winding = winding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW; glFrontFace(winding == vertex::WINDING_CW ? GL_CW : GL_CCW); @@ -1292,7 +1285,7 @@ void Graphics::setBlendState(const BlendState &blend) if (blend.operationRGB == BLENDOP_MAX || blend.operationA == BLENDOP_MAX || blend.operationRGB == BLENDOP_MIN || blend.operationA == BLENDOP_MIN) { - if (!capabilities.features[FEATURE_BLENDMINMAX]) + if (!capabilities.features[FEATURE_BLEND_MINMAX]) throw love::Exception("The 'min' and 'max' blend operations are not supported on this system."); } @@ -1383,10 +1376,10 @@ void Graphics::getAPIStats(int &shaderswitches) const void Graphics::initCapabilities() { - capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = Canvas::isMultiFormatMultiCanvasSupported(); + capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = gl.isMultiFormatMRTSupported(); capabilities.features[FEATURE_CLAMP_ZERO] = gl.isClampZeroOneTextureWrapSupported(); - capabilities.features[FEATURE_BLENDMINMAX] = GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax; - capabilities.features[FEATURE_LIGHTEN] = capabilities.features[FEATURE_BLENDMINMAX]; + capabilities.features[FEATURE_BLEND_MINMAX] = GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax; + capabilities.features[FEATURE_LIGHTEN] = capabilities.features[FEATURE_BLEND_MINMAX]; capabilities.features[FEATURE_FULL_NPOT] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot; capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = gl.isPixelShaderHighpSupported(); capabilities.features[FEATURE_SHADER_DERIVATIVES] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_standard_derivatives; @@ -1400,8 +1393,8 @@ void Graphics::initCapabilities() capabilities.limits[LIMIT_TEXTURE_LAYERS] = gl.getMaxTextureLayers(); capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = gl.getMax3DTextureSize(); capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = gl.getMaxCubeTextureSize(); - capabilities.limits[LIMIT_MULTI_CANVAS] = gl.getMaxRenderTargets(); - capabilities.limits[LIMIT_CANVAS_MSAA] = gl.getMaxRenderbufferSamples(); + capabilities.limits[LIMIT_RENDER_TARGETS] = gl.getMaxRenderTargets(); + capabilities.limits[LIMIT_TEXTURE_MSAA] = gl.getMaxSamples(); capabilities.limits[LIMIT_ANISOTROPY] = gl.getMaxAnisotropy(); static_assert(LIMIT_MAX_ENUM == 8, "Graphics::initCapabilities must be updated when adding a new system limit!"); @@ -1409,19 +1402,124 @@ void Graphics::initCapabilities() capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i); } -bool Graphics::isCanvasFormatSupported(PixelFormat format) const +PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const { - return Canvas::isFormatSupported(format); + switch (format) + { + case PIXELFORMAT_NORMAL: + if (isGammaCorrect()) + return PIXELFORMAT_sRGBA8_UNORM; + else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, rendertarget, readable, sRGB)) + // 32-bit render targets don't have guaranteed support on GLES2. + return PIXELFORMAT_RGBA4_UNORM; + else + return PIXELFORMAT_RGBA8_UNORM; + case PIXELFORMAT_HDR: + return PIXELFORMAT_RGBA16_FLOAT; + default: + return format; + } } -bool Graphics::isCanvasFormatSupported(PixelFormat format, bool readable) const +bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB) { - return Canvas::isFormatSupported(format, readable); -} + if (sRGB && format == PIXELFORMAT_RGBA8_UNORM) + { + format = PIXELFORMAT_sRGBA8_UNORM; + sRGB = false; + } -bool Graphics::isImageFormatSupported(PixelFormat format, bool sRGB) const -{ - return Image::isFormatSupported(format, sRGB); + format = getSizedFormat(format, rendertarget, readable, sRGB); + + OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][sRGB ? 1 : 0]; + + if (supported.hasValue) + return supported.value; + + if (!OpenGL::isPixelFormatSupported(format, rendertarget, readable, sRGB)) + { + supported.set(false); + return supported.value; + } + + if (!rendertarget) + { + supported.set(true); + return supported.value; + } + + // Even though we might have the necessary OpenGL version or extension, + // drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching + // a texture to a FBO whose format the driver doesn't like. So we should + // test with an actual FBO. + GLuint texture = 0; + GLuint renderbuffer = 0; + + // Avoid the test for depth/stencil formats - not every GL version + // guarantees support for depth/stencil-only render targets (which we would + // need for the test below to work), and we already do some finagling in + // convertPixelFormat to try to use the best-supported internal + // depth/stencil format for a particular driver. + if (isPixelFormatDepthStencil(format)) + { + supported.set(true); + return true; + } + + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, sRGB); + + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + + // Make sure at least something is bound to a color attachment. I believe + // this is required on ES2 but I'm not positive. + if (isPixelFormatDepthStencil(format)) + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, 0, 0); + + if (readable) + { + glGenTextures(1, &texture); + gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); + + SamplerState s; + s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST; + gl.setSamplerState(TEXTURE_2D, s); + + gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1); + } + else + { + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); + } + + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment == GL_NONE) + continue; + + if (readable) + gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); + else + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); + } + + supported.set(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + gl.deleteFramebuffer(fbo); + + if (texture != 0) + gl.deleteTexture(texture); + + if (renderbuffer != 0) + glDeleteRenderbuffers(1, &renderbuffer); + + return supported.value; } Shader::Language Graphics::getShaderLanguageTarget() const diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 914a971e8..ac3a707e5 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -36,8 +36,7 @@ #include "image/Image.h" #include "image/ImageData.h" -#include "Image.h" -#include "Canvas.h" +#include "Texture.h" #include "Shader.h" #include "libraries/xxHash/xxhash.h" @@ -60,9 +59,7 @@ public: // Implements Module. const char *getName() const 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::Canvas *newCanvas(const Canvas::Settings &settings) override; + love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override; love::graphics::Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) override; void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override; @@ -73,7 +70,7 @@ public: void draw(const DrawCommand &cmd) override; void draw(const DrawIndexedCommand &cmd) override; - void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, Texture *texture) override; + void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, love::graphics::Texture *texture) override; void clear(OptionalColorf color, OptionalInt stencil, OptionalDouble depth) override; void clear(const std::vector &colors, OptionalInt stencil, OptionalDouble depth) override; @@ -104,9 +101,8 @@ public: void setWireframe(bool enable) override; - bool isCanvasFormatSupported(PixelFormat format) const override; - bool isCanvasFormatSupported(PixelFormat format, bool readable) const override; - bool isImageFormatSupported(PixelFormat format, bool sRGB) const override; + PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const override; + bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override; Renderer getRenderer() const override; bool usesGLSLES() const override; RendererInfo getRendererInfo() const override; @@ -114,7 +110,7 @@ public: Shader::Language getShaderLanguageTarget() const override; // Internal use. - void cleanupCanvas(Canvas *canvas); + void cleanupRenderTexture(love::graphics::Texture *texture); private: @@ -128,7 +124,7 @@ private: for (size_t i = 0; i < rts.colors.size(); i++) hashtargets[hashcount++] = rts.colors[i]; - if (rts.depthStencil.canvas != nullptr) + if (rts.depthStencil.texture != nullptr) hashtargets[hashcount++] = rts.depthStencil; else if (rts.temporaryRTFlags != 0) hashtargets[hashcount++] = RenderTarget(nullptr, -1, rts.temporaryRTFlags); @@ -140,7 +136,7 @@ private: love::graphics::ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) override; love::graphics::Shader *newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) override; love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override; - void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) override; + void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) override; void initCapabilities() override; void getAPIStats(int &shaderswitches) const override; @@ -154,6 +150,9 @@ private: bool windowHasStencil; GLuint mainVAO; + // [rendertarget][readable][srgb] + OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2]; + }; // Graphics } // opengl diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp deleted file mode 100644 index 5bdddeeb3..000000000 --- a/src/modules/graphics/opengl/Image.cpp +++ /dev/null @@ -1,361 +0,0 @@ -/** - * Copyright (c) 2006-2020 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 "Image.h" - -#include "graphics/Graphics.h" -#include "common/int.h" - -// STD -#include // for min/max - -namespace love -{ -namespace graphics -{ -namespace opengl -{ - -Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings) - : love::graphics::Image(textype, format, width, height, slices, settings) - , texture(0) -{ - loadVolatile(); -} - -Image::Image(const Slices &slices, const Settings &settings) - : love::graphics::Image(slices, settings) - , texture(0) -{ - loadVolatile(); -} - -Image::~Image() -{ - unloadVolatile(); -} - -void Image::generateMipmaps() -{ - if (getMipmapCount() > 1 && !isCompressed() && - (GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object)) - { - gl.bindTextureToUnit(this, 0, false); - - GLenum gltextype = OpenGL::getGLTextureType(texType); - - if (gl.bugs.generateMipmapsRequiresTexture2DEnable) - glEnable(gltextype); - - glGenerateMipmap(gltextype); - } -} - -void Image::loadDefaultTexture() -{ - usingDefaultTexture = true; - - gl.bindTextureToUnit(this, 0, false); - setFilter(filter); - - bool isSRGB = false; - gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8_UNORM, 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}; - - int slices = texType == TEXTURE_CUBE ? 6 : 1; - Rect rect = {0, 0, 2, 2}; - for (int slice = 0; slice < slices; slice++) - uploadByteData(PIXELFORMAT_RGBA8_UNORM, px, sizeof(px), 0, slice, rect); -} - -void Image::loadData() -{ - int mipcount = getMipmapCount(); - int slicecount = 1; - - if (texType == TEXTURE_VOLUME) - slicecount = getDepth(); - else if (texType == TEXTURE_2D_ARRAY) - slicecount = getLayerCount(); - else if (texType == TEXTURE_CUBE) - slicecount = 6; - - if (!isCompressed()) - gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers); - - if (mipmapsType == MIPMAPS_GENERATED) - mipcount = 1; - - int w = pixelWidth; - int h = pixelHeight; - int d = depth; - - OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, sRGB); - - for (int mip = 0; mip < mipcount; mip++) - { - if (isCompressed() && (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)) - { - size_t mipsize = 0; - - if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME) - { - for (int slice = 0; slice < data.getSliceCount(mip); slice++) - mipsize += data.get(slice, mip)->getSize(); - } - - GLenum gltarget = OpenGL::getGLTextureType(texType); - glCompressedTexImage3D(gltarget, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr); - } - - for (int slice = 0; slice < slicecount; slice++) - { - love::image::ImageDataBase *id = data.get(slice, mip); - - if (id != nullptr) - uploadImageData(id, mip, slice, 0, 0); - } - - w = std::max(w / 2, 1); - h = std::max(h / 2, 1); - - if (texType == TEXTURE_VOLUME) - d = std::max(d / 2, 1); - } - - if (mipmapsType == MIPMAPS_GENERATED) - generateMipmaps(); -} - -void Image::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) -{ - OpenGL::TempDebugGroup debuggroup("Image data upload"); - - gl.bindTextureToUnit(this, 0, false); - - 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); - } -} - -bool Image::loadVolatile() -{ - if (texture != 0) - return true; - - OpenGL::TempDebugGroup debuggroup("Image load"); - - if (!isCompressed()) - { - // 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) - && mipmapsType != MIPMAPS_DATA) - { - mipmapsType = MIPMAPS_NONE; - filter.mipmap = FILTER_NONE; - } - } - - // NPOT textures don't support mipmapping without full NPOT support. - if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot)) - && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight))) - { - mipmapsType = MIPMAPS_NONE; - filter.mipmap = FILTER_NONE; - } - - glGenTextures(1, &texture); - gl.bindTextureToUnit(this, 0, false); - - // Use a default texture if the size is too big for the system. - if (!validateDimensions(false)) - { - loadDefaultTexture(); - return true; - } - - setFilter(filter); - setWrap(wrap); - setMipmapSharpness(mipmapSharpness); - - 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 - { - loadData(); - - GLenum glerr = glGetError(); - if (glerr != GL_NO_ERROR) - throw love::Exception("Cannot create image (OpenGL error: %s)", OpenGL::errorString(glerr)); - } - catch (love::Exception &) - { - gl.deleteTexture(texture); - texture = 0; - throw; - } - - int64 memsize = 0; - - for (int slice = 0; slice < data.getSliceCount(0); slice++) - memsize += data.get(slice, 0)->getSize(); - - if (getMipmapCount() > 1) - memsize *= 1.33334; - - setGraphicsMemorySize(memsize); - - usingDefaultTexture = false; - return true; -} - -void Image::unloadVolatile() -{ - if (texture == 0) - return; - - gl.deleteTexture(texture); - texture = 0; - - setGraphicsMemorySize(0); -} - -ptrdiff_t Image::getHandle() const -{ - return texture; -} - -void Image::setFilter(const Texture::Filter &f) -{ - Texture::setFilter(f); - - if (!OpenGL::hasTextureFilteringSupport(getPixelFormat())) - { - filter.mag = filter.min = FILTER_NEAREST; - - if (filter.mipmap == FILTER_LINEAR) - filter.mipmap = FILTER_NEAREST; - } - - // We don't want filtering or (attempted) mipmaps on the default texture. - if (usingDefaultTexture) - { - filter.mipmap = FILTER_NONE; - filter.min = filter.mag = FILTER_NEAREST; - } - - gl.bindTextureToUnit(this, 0, false); - gl.setTextureFilter(texType, filter); -} - -bool Image::setWrap(const Texture::Wrap &w) -{ - Graphics::flushStreamDrawsGlobal(); - - 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) || depth != nextP2(depth))) - { - forceclamp = true; - } - - if (forceclamp) - { - if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP) - success = false; - - wrap.s = wrap.t = wrap.r = WRAP_CLAMP; - } - - if (!gl.isClampZeroOneTextureWrapSupported()) - { - if (isClampZeroOrOne(wrap.s)) wrap.s = WRAP_CLAMP; - if (isClampZeroOrOne(wrap.t)) wrap.t = WRAP_CLAMP; - if (isClampZeroOrOne(wrap.r)) wrap.r = WRAP_CLAMP; - } - - gl.bindTextureToUnit(this, 0, false); - gl.setTextureWrap(texType, wrap); - - return success; -} - -bool Image::setMipmapSharpness(float sharpness) -{ - if (!gl.isSamplerLODBiasSupported()) - return false; - - Graphics::flushStreamDrawsGlobal(); - - float maxbias = gl.getMaxLODBias(); - - if (maxbias > 0.01f) - maxbias -= 0.01f; - - mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias); - - gl.bindTextureToUnit(this, 0, false); - - // negative bias is sharper - glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness); - - return true; -} - -bool Image::isFormatSupported(PixelFormat pixelformat, bool sRGB) -{ - return OpenGL::isPixelFormatSupported(pixelformat, false, true, sRGB); -} - -} // opengl -} // graphics -} // love diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 28862602e..c098a67ae 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -23,7 +23,6 @@ #include "OpenGL.h" #include "Shader.h" -#include "Canvas.h" #include "common/Exception.h" #include "graphics/Graphics.h" @@ -103,7 +102,7 @@ OpenGL::OpenGL() , maxCubeTextureSize(0) , maxTextureArrayLayers(0) , maxRenderTargets(1) - , maxRenderbufferSamples(0) + , maxSamples(1) , maxTextureUnits(1) , maxPointSize(1) , coreProfile(false) @@ -260,8 +259,9 @@ void OpenGL::setupContext() #ifdef LOVE_ANDROID // This can't be done in initContext with the rest of the bug checks because - // Canvas::isFormatSupported relies on state initialized here / after init. - if (GLAD_ES_VERSION_3_0 && !Canvas::isFormatSupported(PIXELFORMAT_R8)) + // isPixelFormatSupported relies on state initialized here / after init. + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (GLAD_ES_VERSION_3_0 && gfx != nullptr && !gfx->isPixelFormatSupported(PIXELFORMAT_R8_UNORM, true, true)) bugs.brokenR8PixelFormat = true; #endif } @@ -474,10 +474,10 @@ void OpenGL::initMaxValues() || GLAD_EXT_framebuffer_multisample || GLAD_APPLE_framebuffer_multisample || GLAD_ANGLE_framebuffer_multisample) { - glGetIntegerv(GL_MAX_SAMPLES, &maxRenderbufferSamples); + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); } else - maxRenderbufferSamples = 0; + maxSamples = 1; glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits); @@ -502,11 +502,9 @@ void OpenGL::createDefaultTexture() // untextured primitives vs images. const GLubyte pix[] = {255, 255, 255, 255}; - Texture::Filter filter; - filter.min = filter.mag = Texture::FILTER_NEAREST; - - Texture::Wrap wrap; - wrap.s = wrap.t = wrap.r = Texture::WRAP_CLAMP; + SamplerState s; + s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST; + s.wrapU = s.wrapV = s.wrapW = SamplerState::WRAP_CLAMP; for (int i = 0; i < TEXTURE_MAX_ENUM; i++) { @@ -522,8 +520,7 @@ void OpenGL::createDefaultTexture() glGenTextures(1, &state.defaultTexture[type]); bindTextureToUnit(type, state.defaultTexture[type], 0, false); - setTextureWrap(type, wrap); - setTextureFilter(type, filter); + setSamplerState(type, s); bool isSRGB = false; rawTexStorage(type, 1, PIXELFORMAT_RGBA8_UNORM, isSRGB, 1, 1); @@ -808,13 +805,13 @@ Rect OpenGL::getViewport() const return state.viewport; } -void OpenGL::setScissor(const Rect &v, bool canvasActive) +void OpenGL::setScissor(const Rect &v, bool rtActive) { - if (canvasActive) + if (rtActive) glScissor(v.x, v.y, v.w, v.h); else { - // With no Canvas active, we need to compensate for glScissor starting + // With no RT active, we need to compensate for glScissor starting // from the lower left of the viewport instead of the top left. glScissor(v.x, state.viewport.h - (v.y + v.h), v.w, v.h); } @@ -1050,52 +1047,19 @@ void OpenGL::deleteTexture(GLuint texture) glDeleteTextures(1, &texture); } -void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f) -{ - GLint gmin = f.min == Texture::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR; - GLint gmag = f.mag == Texture::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR; - - if (f.mipmap != Texture::FILTER_NONE) - { - if (f.min == Texture::FILTER_NEAREST && f.mipmap == Texture::FILTER_NEAREST) - gmin = GL_NEAREST_MIPMAP_NEAREST; - else if (f.min == Texture::FILTER_NEAREST && f.mipmap == Texture::FILTER_LINEAR) - gmin = GL_NEAREST_MIPMAP_LINEAR; - else if (f.min == Texture::FILTER_LINEAR && f.mipmap == Texture::FILTER_NEAREST) - gmin = GL_LINEAR_MIPMAP_NEAREST; - else if (f.min == Texture::FILTER_LINEAR && f.mipmap == Texture::FILTER_LINEAR) - gmin = GL_LINEAR_MIPMAP_LINEAR; - else - gmin = GL_LINEAR; - } - - 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(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy); - } - else - f.anisotropy = 1.0f; -} - -GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode) +GLint OpenGL::getGLWrapMode(SamplerState::WrapMode wmode) { switch (wmode) { - case Texture::WRAP_CLAMP: + case SamplerState::WRAP_CLAMP: default: return GL_CLAMP_TO_EDGE; - case Texture::WRAP_CLAMP_ZERO: - case Texture::WRAP_CLAMP_ONE: + case SamplerState::WRAP_CLAMP_ZERO: + case SamplerState::WRAP_CLAMP_ONE: return GL_CLAMP_TO_BORDER; - case Texture::WRAP_REPEAT: + case SamplerState::WRAP_REPEAT: return GL_REPEAT; - case Texture::WRAP_MIRRORED_REPEAT: + case SamplerState::WRAP_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; } } @@ -1125,29 +1089,111 @@ GLint OpenGL::getGLCompareMode(CompareMode mode) } } -static bool isClampOne(Texture::WrapMode mode) +static bool isClampOne(SamplerState::WrapMode mode) { - return mode == Texture::WRAP_CLAMP_ONE; + return mode == SamplerState::WRAP_CLAMP_ONE; } -void OpenGL::setTextureWrap(TextureType target, const graphics::Texture::Wrap &w) +void OpenGL::setSamplerState(TextureType target, SamplerState &s) { - GLenum textype = getGLTextureType(target); + GLenum gltarget = getGLTextureType(target); - if (Texture::isClampZeroOrOne(w.s) || Texture::isClampZeroOrOne(w.t) || Texture::isClampZeroOrOne(w.r)) + GLint gmin = s.minFilter == SamplerState::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR; + GLint gmag = s.magFilter == SamplerState::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR; + + if (s.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE) { - GLfloat c[] = {0.0f, 0.0f, 0.0f, 0.0f}; - if (isClampOne(w.s) || isClampOne(w.t) || isClampOne(w.r)) - c[0] = c[1] = c[2] = c[3] = 1.0f; - - glTexParameterfv(textype, GL_TEXTURE_BORDER_COLOR, c); + if (s.minFilter == SamplerState::FILTER_NEAREST && s.mipmapFilter == SamplerState::MIPMAP_FILTER_NEAREST) + gmin = GL_NEAREST_MIPMAP_NEAREST; + else if (s.minFilter == SamplerState::FILTER_NEAREST && s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + gmin = GL_NEAREST_MIPMAP_LINEAR; + else if (s.minFilter == SamplerState::FILTER_LINEAR && s.mipmapFilter == SamplerState::MIPMAP_FILTER_NEAREST) + gmin = GL_LINEAR_MIPMAP_NEAREST; + else if (s.minFilter == SamplerState::FILTER_LINEAR && s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + gmin = GL_LINEAR_MIPMAP_LINEAR; } - glTexParameteri(textype, GL_TEXTURE_WRAP_S, getGLWrapMode(w.s)); - glTexParameteri(textype, GL_TEXTURE_WRAP_T, getGLWrapMode(w.t)); + glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin); + glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, gmag); + + if (!isClampZeroOneTextureWrapSupported()) + { + if (SamplerState::isClampZeroOrOne(s.wrapU)) s.wrapU = SamplerState::WRAP_CLAMP; + if (SamplerState::isClampZeroOrOne(s.wrapV)) s.wrapV = SamplerState::WRAP_CLAMP; + if (SamplerState::isClampZeroOrOne(s.wrapW)) s.wrapW = SamplerState::WRAP_CLAMP; + } + + if (SamplerState::isClampZeroOrOne(s.wrapU) || SamplerState::isClampZeroOrOne(s.wrapV) || SamplerState::isClampZeroOrOne(s.wrapW)) + { + GLfloat c[] = {0.0f, 0.0f, 0.0f, 0.0f}; + if (isClampOne(s.wrapU) || isClampOne(s.wrapU) || isClampOne(s.wrapV)) + c[0] = c[1] = c[2] = c[3] = 1.0f; + + glTexParameterfv(gltarget, GL_TEXTURE_BORDER_COLOR, c); + } + + glTexParameteri(gltarget, GL_TEXTURE_WRAP_S, getGLWrapMode(s.wrapU)); + glTexParameteri(gltarget, GL_TEXTURE_WRAP_T, getGLWrapMode(s.wrapV)); if (target == TEXTURE_VOLUME) - glTexParameteri(textype, GL_TEXTURE_WRAP_R, getGLWrapMode(w.r)); + glTexParameteri(gltarget, GL_TEXTURE_WRAP_R, getGLWrapMode(s.wrapW)); + + if (isSamplerLODBiasSupported()) + { + float maxbias = getMaxLODBias(); + if (maxbias > 0.01f) + maxbias -= 0.01f; + + s.lodBias = std::min(std::max(s.lodBias, -maxbias), maxbias); + + glTexParameterf(gltarget, GL_TEXTURE_LOD_BIAS, s.lodBias); + } + else + { + s.lodBias = 0.0f; + } + + if (GLAD_EXT_texture_filter_anisotropic) + { + uint8 maxAniso = (uint8) std::min(maxAnisotropy, (float)LOVE_UINT8_MAX); + s.maxAnisotropy = std::min(std::max(s.maxAnisotropy, (uint8)1), maxAniso); + glTexParameteri(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, s.maxAnisotropy); + } + else + { + s.maxAnisotropy = 1; + } + + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0) + { + glTexParameterf(gltarget, GL_TEXTURE_MIN_LOD, (float)s.minLod); + glTexParameterf(gltarget, GL_TEXTURE_MAX_LOD, (float)s.maxLod); + } + else + { + s.minLod = 0; + s.maxLod = LOVE_UINT8_MAX; + } + + if (isDepthCompareSampleSupported()) + { + if (s.depthSampleMode.hasValue) + { + // See the comment in renderstate.h + GLenum glmode = getGLCompareMode(getReversedCompareMode(s.depthSampleMode.value)); + + glTexParameteri(gltarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glTexParameteri(gltarget, GL_TEXTURE_COMPARE_FUNC, glmode); + } + else + { + glTexParameteri(gltarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + } + else + { + s.depthSampleMode.hasValue = false; + } } bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth) @@ -1282,6 +1328,11 @@ bool OpenGL::isBaseVertexSupported() const return baseVertexSupported; } +bool OpenGL::isMultiFormatMRTSupported() const +{ + return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object); +} + int OpenGL::getMax2DTextureSize() const { return std::max(max2DTextureSize, 1); @@ -1307,9 +1358,9 @@ int OpenGL::getMaxRenderTargets() const return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS); } -int OpenGL::getMaxRenderbufferSamples() const +int OpenGL::getMaxSamples() const { - return maxRenderbufferSamples; + return maxSamples; } int OpenGL::getMaxTextureUnits() const @@ -1743,10 +1794,10 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)); } else - return GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB; + return GLAD_ES_VERSION_3_0; } else - return GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB; + return GLAD_ES_VERSION_3_0 || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB; case PIXELFORMAT_R16_UNORM: case PIXELFORMAT_RG16_UNORM: return GLAD_VERSION_3_0 @@ -1946,7 +1997,7 @@ const char *OpenGL::framebufferStatusString(GLenum status) case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: return "Error in graphics driver (incomplete read buffer)"; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: - return "Canvas with the specified MSAA count cannot be rendered to on this system."; + return "Texture with the specified MSAA count cannot be rendered to on this system."; case GL_FRAMEBUFFER_UNSUPPORTED: return "Renderable textures are unsupported"; default: diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 8dd975cec..ad02098ff 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -261,7 +261,7 @@ public: * Sets the scissor box to the specified rectangle. * The y-coordinate starts at the top and is flipped internally. **/ - void setScissor(const Rect &v, bool canvasActive); + void setScissor(const Rect &v, bool rtActive); /** * Sets the global point size. @@ -329,16 +329,9 @@ public: void deleteTexture(GLuint texture); /** - * Sets the texture filter mode for the currently bound texture. - * The anisotropy parameter of the argument is set to the actual amount of - * anisotropy that was used. + * Sets sampler state parameters for the currently bound texture. **/ - void setTextureFilter(TextureType target, graphics::Texture::Filter &f); - - /** - * Sets the texture wrap mode for the currently bound texture. - **/ - void setTextureWrap(TextureType target, const graphics::Texture::Wrap &w); + void setSamplerState(TextureType target, SamplerState &s); /** * Equivalent to glTexStorage2D/3D on platforms that support it. Equivalent @@ -354,6 +347,7 @@ public: bool isDepthCompareSampleSupported() const; bool isSamplerLODBiasSupported() const; bool isBaseVertexSupported() const; + bool isMultiFormatMRTSupported() const; /** * Returns the maximum supported width or height of a texture. @@ -369,9 +363,9 @@ public: int getMaxRenderTargets() const; /** - * Returns the maximum supported number of MSAA samples for renderbuffers. + * Returns the maximum supported number of MSAA sampless. **/ - int getMaxRenderbufferSamples() const; + int getMaxSamples() const; /** * Returns the maximum number of accessible texture units. @@ -407,7 +401,7 @@ public: static GLenum getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat); static GLenum getGLBufferUsage(vertex::Usage usage); static GLenum getGLTextureType(TextureType type); - static GLint getGLWrapMode(Texture::WrapMode wmode); + static GLint getGLWrapMode(SamplerState::WrapMode wmode); static GLint getGLCompareMode(CompareMode mode); static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB); @@ -442,7 +436,7 @@ private: int maxCubeTextureSize; int maxTextureArrayLayers; int maxRenderTargets; - int maxRenderbufferSamples; + int maxSamples; int maxTextureUnits; float maxPointSize; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 20f46323d..cc9f64a7f 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -196,7 +196,7 @@ void Shader::mapActiveUniforms() glUniform1iv(u.location, u.count, u.ints); - u.textures = new Texture*[u.count]; + u.textures = new love::graphics::Texture*[u.count]; memset(u.textures, 0, sizeof(Texture *) * u.count); } } @@ -565,12 +565,12 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalupda } } -void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count) +void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) { Shader::sendTextures(info, textures, count, false); } -void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate) +void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count, bool internalUpdate) { if (info->baseType != UNIFORM_SAMPLER) return; @@ -585,10 +585,11 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count // Bind the textures to the texture units. for (int i = 0; i < count; i++) { - Texture *tex = textures[i]; + love::graphics::Texture *tex = textures[i]; if (tex != nullptr) { + const SamplerState &sampler = tex->getSamplerState(); if (!tex->isReadable()) { if (internalUpdate) @@ -596,7 +597,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count else throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader."); } - else if (info->isDepthSampler != tex->getDepthSampleMode().hasValue) + else if (info->isDepthSampler != sampler.depthSampleMode.hasValue) { if (internalUpdate) continue; @@ -671,7 +672,7 @@ int Shader::getVertexAttributeIndex(const std::string &name) return location; } -void Shader::setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) +void Shader::setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) { const BuiltinUniform builtins[3] = { BUILTIN_TEXTURE_VIDEO_Y, @@ -679,7 +680,7 @@ void Shader::setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *cr BUILTIN_TEXTURE_VIDEO_CR, }; - Texture *textures[3] = {ytexture, cbtexture, crtexture}; + love::graphics::Texture *textures[3] = {ytexture, cbtexture, crtexture}; for (int i = 0; i < 3; i++) { @@ -734,8 +735,8 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, // The shader does pixcoord.y = gl_FragCoord.y * params.z + params.w. // This lets us flip pixcoord.y when needed, to be consistent (drawing - // with no Canvas active makes the pixel coordinates y-flipped.) - if (gfx->isCanvasActive()) + // with no RT active makes the pixel coordinates y-flipped.) + if (gfx->isRenderTargetActive()) { // No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0. data.screenSizeParams.z = 1.0f; diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 8522716bd..8681ca5b1 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -61,10 +61,10 @@ public: const UniformInfo *getUniformInfo(const std::string &name) const 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; + void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) override; bool hasUniform(const std::string &name) const override; ptrdiff_t getHandle() const override; - void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) override; + void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override; void updatePointSize(float size); void updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH); @@ -82,7 +82,7 @@ private: void mapActiveUniforms(); void updateUniform(const UniformInfo *info, int count, bool internalupdate); - void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalupdate); + void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count, bool internalupdate); int getUniformTypeComponents(GLenum type) const; MatrixSize getMatrixSize(GLenum type) const; @@ -110,7 +110,7 @@ private: // Uniform location buffer map std::map uniforms; - // Texture unit pool for setting images + // Texture unit pool for setting textures std::vector textureUnits; std::vector> pendingUniformUpdates; diff --git a/src/modules/graphics/opengl/StreamBuffer.cpp b/src/modules/graphics/opengl/StreamBuffer.cpp index 55abe2718..966fa2a1f 100644 --- a/src/modules/graphics/opengl/StreamBuffer.cpp +++ b/src/modules/graphics/opengl/StreamBuffer.cpp @@ -406,7 +406,12 @@ public: if (!alignedMalloc((void **) &data, alignedSize, alignment)) throw love::Exception("Out of memory."); - loadVolatile(); + if (!loadVolatile()) + { + ptrdiff_t pointer = (ptrdiff_t) data; + alignedFree(data); + throw love::Exception("AMD Pinned Memory StreamBuffer implementation failed to create buffer (address: %p, alignment: %ld, aiigned size: %ld)", pointer, alignment, alignedSize); + } } ~StreamBufferPinnedMemory() @@ -441,9 +446,19 @@ public: glGenBuffers(1, &vbo); + while (glGetError() != GL_NO_ERROR) + /* Clear errors. */; + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, vbo); glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, alignedSize, data, GL_STREAM_DRAW); + if (glGetError() != GL_NO_ERROR) + { + gl.deleteBuffer(vbo); + vbo = 0; + return false; + } + frameGPUReadOffset = 0; frameIndex = 0; @@ -485,8 +500,22 @@ love::graphics::StreamBuffer *CreateStreamBuffer(BufferType mode, size_t size) // AMD's pinned memory seems to be faster than persistent mapping, // on AMD GPUs. if (GLAD_AMD_pinned_memory) - return new StreamBufferPinnedMemory(mode, size); - else if (GLAD_VERSION_4_4 || GLAD_ARB_buffer_storage) + { + try + { + return new StreamBufferPinnedMemory(mode, size); + } + catch (love::Exception &) + { + // According to the spec, pinned memory can fail if the RAM + // allocation can't be mapped to the GPU's address space. + // This seems to happen in practice on Mesa + amdgpu: + // https://bitbucket.org/rude/love/issues/1540 + // Fall through to other implementations when that happens. + } + } + + if (GLAD_VERSION_4_4 || GLAD_ARB_buffer_storage) return new StreamBufferPersistentMapSync(mode, size); // Most modern drivers have a separate internal thread which queues diff --git a/src/modules/graphics/opengl/Texture.cpp b/src/modules/graphics/opengl/Texture.cpp new file mode 100644 index 000000000..b54b42eb0 --- /dev/null +++ b/src/modules/graphics/opengl/Texture.cpp @@ -0,0 +1,568 @@ +/** + * Copyright (c) 2006-2020 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 "Texture.h" + +#include "graphics/Graphics.h" +#include "Graphics.h" +#include "common/int.h" + +// STD +#include // for min/max + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + +static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers, bool clear) +{ + // get currently bound fbo to reset to it later + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + + glGenFramebuffers(1, &framebuffer); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); + + if (texture != 0) + { + if (isPixelFormatDepthStencil(format) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0)) + { + // glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3. + GLenum none = GL_NONE; + if (GLAD_ES_VERSION_3_0) + glDrawBuffers(1, &none); + else + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + + bool unusedSRGB = false; + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB); + + int faces = texType == TEXTURE_CUBE ? 6 : 1; + + // 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--) + { + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment == GL_NONE) + continue; + + gl.framebufferTexture(attachment, texType, texture, 0, layer, face); + } + + if (clear) + { + if (isPixelFormatDepthStencil(format)) + { + bool hadDepthWrites = gl.hasDepthWrites(); + if (!hadDepthWrites) // glDepthMask also affects glClear. + gl.setDepthWrites(true); + + gl.clearDepth(1.0); + glClearStencil(0); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + if (!hadDepthWrites) + gl.setDepthWrites(hadDepthWrites); + } + else + { + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + } + } + } + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + return status; +} + +static GLenum newRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer) +{ + bool unusedSRGB = false; + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true, unusedSRGB); + + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + + // Temporary FBO used to clear the renderbuffer. + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + + if (isPixelFormatDepthStencil(pixelformat) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0)) + { + // glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3. + GLenum none = GL_NONE; + if (GLAD_ES_VERSION_3_0) + glDrawBuffers(1, &none); + else + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + + glGenRenderbuffers(1, &buffer); + glBindRenderbuffer(GL_RENDERBUFFER, buffer); + + if (samples > 1) + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height); + else + glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, width, height); + + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment != GL_NONE) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffer); + } + + if (samples > 1) + { + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + samples = std::max(1, samples); + } + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status == GL_FRAMEBUFFER_COMPLETE) + { + if (isPixelFormatDepthStencil(pixelformat)) + { + bool hadDepthWrites = gl.hasDepthWrites(); + if (!hadDepthWrites) // glDepthMask also affects glClear. + gl.setDepthWrites(true); + + gl.clearDepth(1.0); + glClearStencil(0); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + if (!hadDepthWrites) + gl.setDepthWrites(hadDepthWrites); + } + else + { + // Initialize the buffer to transparent black. + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + } + else + { + glDeleteRenderbuffers(1, &buffer); + buffer = 0; + samples = 1; + } + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + gl.deleteFramebuffer(fbo); + + return status; +} + +Texture::Texture(const Settings &settings, const Slices *data) + : love::graphics::Texture(settings, data) + , slices(settings.type) + , fbo(0) + , texture(0) + , renderbuffer(0) + , framebufferStatus(GL_FRAMEBUFFER_COMPLETE) + , textureGLError(GL_NO_ERROR) + , actualSamples(1) +{ + if (data != nullptr) + slices = *data; + + if (!loadVolatile()) + { + if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE) + throw love::Exception("Cannot create Texture (OpenGL framebuffer error: %s)", OpenGL::framebufferStatusString(framebufferStatus)); + if (textureGLError != GL_NO_ERROR) + throw love::Exception("Cannot create Texture (OpenGL error: %s)", OpenGL::errorString(textureGLError)); + } +} + +Texture::~Texture() +{ + unloadVolatile(); +} + +void Texture::createTexture() +{ + // The base class handles some validation. For example, if ImageData is + // given then it must exist for all mip levels, a render target can't use + // a compressed format, etc. + + glGenTextures(1, &texture); + gl.bindTextureToUnit(this, 0, false); + + // Use a default texture if the size is too big for the system. + // validateDimensions is also called in the base class for RTs and + // non-readable textures. + if (!renderTarget && !validateDimensions(false)) + { + usingDefaultTexture = true; + + setSamplerState(samplerState); + + bool isSRGB = false; + gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8_UNORM, 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}; + + int slices = texType == TEXTURE_CUBE ? 6 : 1; + Rect rect = {0, 0, 2, 2}; + for (int slice = 0; slice < slices; slice++) + uploadByteData(PIXELFORMAT_RGBA8_UNORM, px, sizeof(px), 0, slice, rect, nullptr); + + return; + } + + GLenum gltype = OpenGL::getGLTextureType(texType); + if (renderTarget && GLAD_ANGLE_texture_usage) + glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); + + setSamplerState(samplerState); + + int mipcount = getMipmapCount(); + int slicecount = 1; + + if (texType == TEXTURE_VOLUME) + slicecount = getDepth(); + else if (texType == TEXTURE_2D_ARRAY) + slicecount = getLayerCount(); + else if (texType == TEXTURE_CUBE) + slicecount = 6; + + // For a couple flimsy reasons, we don't initialize the texture here if it's + // compressed. I need to verify that getPixelFormatSliceSize will return the + // correct value for all compressed texture formats, and I also vaguely + // remember some driver issues on some old Android systems, maybe... + // For now, the base class enforces data on init for compressed textures. + if (!isCompressed()) + gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers); + + int w = pixelWidth; + int h = pixelHeight; + int d = depth; + + OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, sRGB); + + for (int mip = 0; mip < mipcount; mip++) + { + if (isCompressed() && (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)) + { + size_t mipsize = 0; + + if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME) + { + for (int slice = 0; slice < slices.getSliceCount(mip); slice++) + { + auto id = slices.get(slice, mip); + if (id != nullptr) + mipsize += id->getSize(); + } + } + + if (mipsize > 0) + glCompressedTexImage3D(gltype, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr); + } + + for (int slice = 0; slice < slicecount; slice++) + { + love::image::ImageDataBase *id = slices.get(slice, mip); + if (id != nullptr) + uploadImageData(id, mip, slice, 0, 0); + } + + w = std::max(w / 2, 1); + h = std::max(h / 2, 1); + + if (texType == TEXTURE_VOLUME) + d = std::max(d / 2, 1); + } + + bool hasdata = slices.get(0, 0) != nullptr; + + // Create a local FBO used for glReadPixels as well as MSAA blitting. + if (isRenderTarget()) + { + bool clear = !hasdata; + int slices = texType == TEXTURE_VOLUME ? depth : layers; + framebufferStatus = createFBO(fbo, texType, format, texture, slices, clear); + } + else if (!hasdata) + { + // Initialize all slices to transparent black. + std::vector emptydata(getPixelFormatSliceSize(format, w, h)); + + Rect r = {0, 0, w, h}; + int slices = texType == TEXTURE_VOLUME ? depth : layers; + slices = texType == TEXTURE_CUBE ? 6 : slices; + for (int i = 0; i < slices; i++) + uploadByteData(format, emptydata.data(), emptydata.size(), 0, i, r); + } + + // Non-readable textures can't have mipmaps (enforced in the base class), + // so generateMipmaps here is fine - when they aren't already initialized. + if (getMipmapCount() > 1 && slices.getMipmapCount() <= 1) + generateMipmaps(); +} + +bool Texture::loadVolatile() +{ + if (texture != 0 || renderbuffer != 0) + return true; + + OpenGL::TempDebugGroup debuggroup("Texture load"); + + // NPOT textures don't support mipmapping without full NPOT support. + if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot)) + && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight))) + { + mipmapCount = 1; + samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + } + + actualSamples = std::max(1, std::min(getRequestedMSAA(), gl.getMaxSamples())); + + while (glGetError() != GL_NO_ERROR); // Clear errors. + + framebufferStatus = GL_FRAMEBUFFER_COMPLETE; + textureGLError = GL_NO_ERROR; + + if (isReadable()) + createTexture(); + + if (!usingDefaultTexture && framebufferStatus == GL_FRAMEBUFFER_COMPLETE + && (!isReadable() || actualSamples > 1)) + { + framebufferStatus = newRenderbuffer(pixelWidth, pixelHeight, actualSamples, format, renderbuffer); + } + + textureGLError = glGetError(); + + if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE || textureGLError != GL_NO_ERROR) + { + unloadVolatile(); + return false; + } + + int64 memsize = 0; + + for (int mip = 0; mip < getMipmapCount(); mip++) + { + int w = getPixelWidth(mip); + int h = getPixelHeight(mip); + int slices = getDepth(mip) * layers * (texType == TEXTURE_CUBE ? 6 : 1); + memsize += getPixelFormatSliceSize(format, w, h) * slices; + } + + if (actualSamples > 1 && isReadable()) + { + int slices = depth * layers * (texType == TEXTURE_CUBE ? 6 : 1); + memsize += getPixelFormatSliceSize(format, pixelWidth, pixelHeight) * slices * actualSamples; + } + else if (actualSamples > 1) + memsize *= actualSamples; + + setGraphicsMemorySize(memsize); + + usingDefaultTexture = false; + return true; +} + +void Texture::unloadVolatile() +{ + if (isRenderTarget() && (fbo != 0 || renderbuffer != 0 || texture != 0)) + { + // This is a bit ugly, but we need some way to destroy the cached FBO + // when this texture's GL object is destroyed. + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr) + gfx->cleanupRenderTexture(this); + } + + if (fbo != 0) + gl.deleteFramebuffer(fbo); + + if (renderbuffer != 0) + glDeleteRenderbuffers(1, &renderbuffer); + + if (texture != 0) + gl.deleteTexture(texture); + + fbo = 0; + renderbuffer = 0; + texture = 0; + + setGraphicsMemorySize(0); +} + +void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd) +{ + love::image::ImageDataBase *oldd = slices.get(slice, level); + + // We can only replace the internal Data (used when reloading due to setMode) + // if the dimensions match. + if (imgd != nullptr && oldd != nullptr && oldd->getWidth() == imgd->getWidth() + && oldd->getHeight() == imgd->getHeight()) + { + slices.set(slice, level, imgd); + } + + OpenGL::TempDebugGroup debuggroup("Texture data upload"); + + gl.bindTextureToUnit(this, 0, false); + + 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 textures."); + + 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 Texture::generateMipmaps() +{ + if (getMipmapCount() == 1 || getMipmapsMode() == MIPMAPS_NONE) + throw love::Exception("generateMipmaps can only be called on a Texture which was created with mipmaps enabled."); + + if (isPixelFormatCompressed(format)) + throw love::Exception("generateMipmaps cannot be called on a compressed Texture."); + + gl.bindTextureToUnit(this, 0, false); + + GLenum gltextype = OpenGL::getGLTextureType(texType); + + if (gl.bugs.generateMipmapsRequiresTexture2DEnable) + glEnable(gltextype); + + glGenerateMipmap(gltextype); +} + +love::image::ImageData *Texture::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r) +{ + // Base class does validation (only RTs allowed, etc) and creates ImageData. + love::image::ImageData *data = love::graphics::Texture::newImageData(module, slice, mipmap, r); + + if (fbo == 0) // Should never be reached. + return data; + + bool isSRGB = false; + OpenGL::TextureFormat fmt = gl.convertPixelFormat(data->getFormat(), false, isSRGB); + + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO()); + + if (slice > 0 || mipmap > 0) + { + int layer = texType == TEXTURE_CUBE ? 0 : slice; + int face = texType == TEXTURE_CUBE ? slice : 0; + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face); + } + + glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data->getData()); + + if (slice > 0 || mipmap > 0) + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + + return data; +} + +void Texture::setSamplerState(const SamplerState &s) +{ + if (s.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported()) + throw love::Exception("Depth comparison sampling in shaders is not supported on this system."); + + // Base class does common validation and assigns samplerState. + love::graphics::Texture::setSamplerState(s); + + if (!OpenGL::hasTextureFilteringSupport(getPixelFormat())) + { + samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST; + + if (samplerState.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST; + } + + // We don't want filtering or (attempted) mipmaps on the default texture. + if (usingDefaultTexture) + { + samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + samplerState.minFilter = samplerState.magFilter = SamplerState::FILTER_NEAREST; + } + + // 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) || depth != nextP2(depth))) + { + samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP; + } + + gl.bindTextureToUnit(this, 0, false); + gl.setSamplerState(texType, samplerState); +} + +ptrdiff_t Texture::getHandle() const +{ + return texture; +} + +ptrdiff_t Texture::getRenderTargetHandle() const +{ + return renderTarget ? (renderbuffer != 0 ? renderbuffer : texture) : 0; +} + +} // opengl +} // graphics +} // love diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Texture.h similarity index 64% rename from src/modules/graphics/opengl/Image.h rename to src/modules/graphics/opengl/Texture.h index a1b203cfb..4f0d871d3 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Texture.h @@ -21,7 +21,7 @@ #pragma once // LOVE -#include "graphics/Image.h" +#include "graphics/Texture.h" #include "graphics/Volatile.h" // OpenGL @@ -34,40 +34,47 @@ namespace graphics namespace opengl { -class Image final : public love::graphics::Image, public Volatile +class Texture final : public love::graphics::Texture, public Volatile { public: - Image(const Slices &data, const Settings &settings); - Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings); + Texture(const Settings &settings, const Slices *data); - virtual ~Image(); + virtual ~Texture(); // Implements Volatile. bool loadVolatile() override; void unloadVolatile() override; + void generateMipmaps() override; + love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect) override; + void setSamplerState(const SamplerState &s) override; + ptrdiff_t getHandle() const override; + ptrdiff_t getRenderTargetHandle() const override; + int getMSAA() const override { return actualSamples; } - void setFilter(const Texture::Filter &f) override; - bool setWrap(const Texture::Wrap &w) override; - - bool setMipmapSharpness(float sharpness) override; - - static bool isFormatSupported(PixelFormat pixelformat, bool sRGB); + inline GLuint getFBO() const { return fbo; } private: - void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override; - void generateMipmaps() override; + void createTexture(); - void loadDefaultTexture(); - void loadData(); + void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd = nullptr) override; + + Slices slices; + + GLuint fbo; - // OpenGL texture identifier. GLuint texture; + GLuint renderbuffer; -}; // Image + GLenum framebufferStatus; + GLenum textureGLError; + + int actualSamples; + +}; // Texture } // opengl } // graphics diff --git a/src/modules/graphics/wrap_Canvas.cpp b/src/modules/graphics/wrap_Canvas.cpp deleted file mode 100644 index fa020f485..000000000 --- a/src/modules/graphics/wrap_Canvas.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright (c) 2006-2020 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 "wrap_Canvas.h" -#include "Graphics.h" - -namespace love -{ -namespace graphics -{ - -Canvas *luax_checkcanvas(lua_State *L, int idx) -{ - return luax_checktype(L, idx); -} - -int w_Canvas_getMSAA(lua_State *L) -{ - Canvas *canvas = luax_checkcanvas(L, 1); - lua_pushinteger(L, canvas->getMSAA()); - return 1; -} - -int w_Canvas_renderTo(lua_State *L) -{ - Graphics::RenderTarget rt(luax_checkcanvas(L, 1)); - - int startidx = 2; - - if (rt.canvas->getTextureType() != TEXTURE_2D) - { - rt.slice = (int) luaL_checkinteger(L, 2) - 1; - startidx++; - } - - luaL_checktype(L, startidx, LUA_TFUNCTION); - - auto graphics = Module::getInstance(Module::M_GRAPHICS); - - if (graphics) - { - // Save the current render targets so we can restore them when we're done. - Graphics::RenderTargets oldtargets = graphics->getCanvas(); - - for (auto c : oldtargets.colors) - c.canvas->retain(); - - if (oldtargets.depthStencil.canvas != nullptr) - oldtargets.depthStencil.canvas->retain(); - - luax_catchexcept(L, [&](){ graphics->setCanvas(rt, false); }); - - lua_settop(L, 2); // make sure the function is on top of the stack - int status = lua_pcall(L, 0, 0, 0); - - graphics->setCanvas(oldtargets); - - for (auto c : oldtargets.colors) - c.canvas->release(); - - if (oldtargets.depthStencil.canvas != nullptr) - oldtargets.depthStencil.canvas->release(); - - if (status != 0) - return lua_error(L); - } - - return 0; -} - -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 slice = 0; - int mipmap = 0; - Rect rect = {0, 0, canvas->getPixelWidth(), canvas->getPixelHeight()}; - - if (canvas->getTextureType() != TEXTURE_2D) - slice = (int) luaL_checkinteger(L, 2) - 1; - - mipmap = (int) luaL_optinteger(L, 3, 1) - 1; - - if (!lua_isnoneornil(L, 4)) - { - rect.x = (int) luaL_checkinteger(L, 4); - rect.y = (int) luaL_checkinteger(L, 5); - rect.w = (int) luaL_checkinteger(L, 6); - rect.h = (int) luaL_checkinteger(L, 7); - } - - love::image::ImageData *img = nullptr; - luax_catchexcept(L, [&](){ img = canvas->newImageData(image, slice, mipmap, rect); }); - - luax_pushtype(L, img); - img->release(); - return 1; -} - -int w_Canvas_generateMipmaps(lua_State *L) -{ - Canvas *c = luax_checkcanvas(L, 1); - luax_catchexcept(L, [&]() { c->generateMipmaps(); }); - return 0; -} - -int w_Canvas_getMipmapMode(lua_State *L) -{ - Canvas *c = luax_checkcanvas(L, 1); - const char *str; - if (!Canvas::getConstant(c->getMipmapMode(), str)) - return luax_enumerror(L, "mipmap mode", Canvas::getConstants(Canvas::MIPMAPS_MAX_ENUM), str); - - lua_pushstring(L, str); - return 1; -} - -static const luaL_Reg w_Canvas_functions[] = -{ - { "getMSAA", w_Canvas_getMSAA }, - { "renderTo", w_Canvas_renderTo }, - { "newImageData", w_Canvas_newImageData }, - { "generateMipmaps", w_Canvas_generateMipmaps }, - { "getMipmapMode", w_Canvas_getMipmapMode }, - { 0, 0 } -}; - -extern "C" int luaopen_canvas(lua_State *L) -{ - return luax_register_type(L, &Canvas::type, w_Texture_functions, w_Canvas_functions, nullptr); -} - -} // graphics -} // love diff --git a/src/modules/graphics/wrap_Canvas.h b/src/modules/graphics/wrap_Canvas.h deleted file mode 100644 index a56eb083c..000000000 --- a/src/modules/graphics/wrap_Canvas.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2006-2020 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. - **/ - -#pragma once - -// LOVE -#include "common/runtime.h" -#include "Canvas.h" -#include "wrap_Texture.h" - -namespace love -{ -namespace graphics -{ - -//see Canvas.h -Canvas *luax_checkcanvas(lua_State *L, int idx); -extern "C" int luaopen_canvas(lua_State *L); - -} // graphics -} // love diff --git a/src/modules/graphics/wrap_Font.cpp b/src/modules/graphics/wrap_Font.cpp index 115a84348..468d67fe2 100644 --- a/src/modules/graphics/wrap_Font.cpp +++ b/src/modules/graphics/wrap_Font.cpp @@ -139,33 +139,33 @@ int w_Font_getLineHeight(lua_State *L) int w_Font_setFilter(lua_State *L) { Font *t = luax_checkfont(L, 1); - Texture::Filter f = t->getFilter(); + SamplerState s = t->getSamplerState(); const char *minstr = luaL_checkstring(L, 2); const char *magstr = luaL_optstring(L, 3, minstr); - if (!Texture::getConstant(minstr, f.min)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr); - if (!Texture::getConstant(magstr, f.mag)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr); + if (!SamplerState::getConstant(minstr, s.minFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr); + if (!SamplerState::getConstant(magstr, s.magFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr); - f.anisotropy = (float) luaL_optnumber(L, 4, 1.0); + s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX); - luax_catchexcept(L, [&](){ t->setFilter(f); }); + luax_catchexcept(L, [&](){ t->setSamplerState(s); }); return 0; } int w_Font_getFilter(lua_State *L) { Font *t = luax_checkfont(L, 1); - const Texture::Filter f = t->getFilter(); + const SamplerState &s = t->getSamplerState(); const char *minstr; const char *magstr; - Texture::getConstant(f.min, minstr); - Texture::getConstant(f.mag, magstr); + SamplerState::getConstant(s.minFilter, minstr); + SamplerState::getConstant(s.magFilter, magstr); lua_pushstring(L, minstr); lua_pushstring(L, magstr); - lua_pushnumber(L, f.anisotropy); + lua_pushnumber(L, s.maxAnisotropy); return 3; } diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index e25762744..d2c09e0c3 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -169,7 +169,7 @@ int w_discard(lua_State *L) else { bool discardcolor = luax_optboolean(L, 1, true); - size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().colors.size()); + size_t numbuffers = std::max((size_t) 1, instance()->getRenderTargets().colors.size()); colorbuffers = std::vector(numbuffers, discardcolor); } @@ -249,10 +249,10 @@ int w_getDPIScale(lua_State *L) static Graphics::RenderTarget checkRenderTarget(lua_State *L, int idx) { lua_rawgeti(L, idx, 1); - Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0); + Graphics::RenderTarget target(luax_checktexture(L, -1), 0); lua_pop(L, 1); - TextureType type = target.canvas->getTextureType(); + TextureType type = target.texture->getTextureType(); if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) target.slice = luax_checkintflag(L, idx, "layer") - 1; else if (type == TEXTURE_CUBE) @@ -263,7 +263,7 @@ static Graphics::RenderTarget checkRenderTarget(lua_State *L, int idx) return target; } -int w_setCanvas(lua_State *L) +int w_setRenderTarget(lua_State *L) { // Disable stencil writes. luax_catchexcept(L, [](){ instance()->stopDrawToStencilBuffer(); }); @@ -271,7 +271,7 @@ int w_setCanvas(lua_State *L) // called with none -> reset to default buffer if (lua_isnoneornil(L, 1)) { - instance()->setCanvas(); + instance()->setRenderTarget(); return 0; } @@ -292,10 +292,10 @@ int w_setCanvas(lua_State *L) targets.colors.push_back(checkRenderTarget(L, -1)); else { - targets.colors.emplace_back(luax_checkcanvas(L, -1), 0); + targets.colors.emplace_back(luax_checktexture(L, -1), 0); - if (targets.colors.back().canvas->getTextureType() != TEXTURE_2D) - return luaL_error(L, "Non-2D canvases must use the table-of-tables variant of setCanvas."); + if (targets.colors.back().texture->getTextureType() != TEXTURE_2D) + return luaL_error(L, "Non-2D textures must use the table-of-tables variant of setRenderTargets."); } lua_pop(L, 1); @@ -311,21 +311,21 @@ int w_setCanvas(lua_State *L) else if (dstype == LUA_TBOOLEAN) targets.temporaryRTFlags |= luax_toboolean(L, -1) ? (tempdepthflag | tempstencilflag) : 0; else if (dstype != LUA_TNONE && dstype != LUA_TNIL) - targets.depthStencil.canvas = luax_checkcanvas(L, -1); + targets.depthStencil.texture = luax_checktexture(L, -1); lua_pop(L, 1); - if (targets.depthStencil.canvas == nullptr && (targets.temporaryRTFlags & tempdepthflag) == 0) + if (targets.depthStencil.texture == nullptr && (targets.temporaryRTFlags & tempdepthflag) == 0) targets.temporaryRTFlags |= luax_boolflag(L, 1, "depth", false) ? tempdepthflag : 0; - if (targets.depthStencil.canvas == nullptr && (targets.temporaryRTFlags & tempstencilflag) == 0) + if (targets.depthStencil.texture == nullptr && (targets.temporaryRTFlags & tempstencilflag) == 0) targets.temporaryRTFlags |= luax_boolflag(L, 1, "stencil", false) ? tempstencilflag : 0; } else { for (int i = 1; i <= lua_gettop(L); i++) { - Graphics::RenderTarget target(luax_checkcanvas(L, i), 0); - TextureType type = target.canvas->getTextureType(); + Graphics::RenderTarget target(luax_checktexture(L, i), 0); + TextureType type = target.texture->getTextureType(); if (i == 1 && type != TEXTURE_2D) { @@ -341,30 +341,36 @@ int w_setCanvas(lua_State *L) } if (i > 1 && type != TEXTURE_2D) - return luaL_error(L, "This variant of setCanvas only supports 2D texture types."); + return luaL_error(L, "This variant of setRenderTarget only supports 2D texture types."); targets.colors.push_back(target); } } luax_catchexcept(L, [&]() { - if (targets.getFirstTarget().canvas != nullptr) - instance()->setCanvas(targets); + if (targets.getFirstTarget().texture != nullptr) + instance()->setRenderTargets(targets); else - instance()->setCanvas(); + instance()->setRenderTarget(); }); return 0; } +int w_setCanvas(lua_State *L) +{ + luax_markdeprecated(L, "love.graphics.setCanvas", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.setRenderTarget"); + return w_setRenderTarget(L); +} + static void pushRenderTarget(lua_State *L, const Graphics::RenderTarget &rt) { lua_createtable(L, 1, 2); - luax_pushtype(L, rt.canvas); + luax_pushtype(L, rt.texture); lua_rawseti(L, -2, 1); - TextureType type = rt.canvas->getTextureType(); + TextureType type = rt.texture->getTextureType(); if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) { @@ -381,9 +387,9 @@ static void pushRenderTarget(lua_State *L, const Graphics::RenderTarget &rt) lua_setfield(L, -2, "mipmap"); } -int w_getCanvas(lua_State *L) +int w_getRenderTarget(lua_State *L) { - Graphics::RenderTargets targets = instance()->getCanvas(); + Graphics::RenderTargets targets = instance()->getRenderTargets(); int ntargets = (int) targets.colors.size(); if (ntargets == 0) @@ -392,13 +398,13 @@ int w_getCanvas(lua_State *L) return 1; } - bool shouldUseTablesVariant = targets.depthStencil.canvas != nullptr; + bool shouldUseTablesVariant = targets.depthStencil.texture != nullptr; if (!shouldUseTablesVariant) { for (const auto &rt : targets.colors) { - if (rt.mipmap != 0 || rt.canvas->getTextureType() != TEXTURE_2D) + if (rt.mipmap != 0 || rt.texture->getTextureType() != TEXTURE_2D) { shouldUseTablesVariant = true; break; @@ -416,7 +422,7 @@ int w_getCanvas(lua_State *L) lua_rawseti(L, -2, i + 1); } - if (targets.depthStencil.canvas != nullptr) + if (targets.depthStencil.texture != nullptr) { pushRenderTarget(L, targets.depthStencil); lua_setfield(L, -2, "depthstencil"); @@ -427,12 +433,18 @@ int w_getCanvas(lua_State *L) else { for (const auto &rt : targets.colors) - luax_pushtype(L, rt.canvas); + luax_pushtype(L, rt.texture); return ntargets; } } +int w_getCanvas(lua_State *L) +{ + luax_markdeprecated(L, "love.graphics.getCanvas", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.getRenderTarget"); + return w_getRenderTarget(L); +} + static void screenshotFunctionCallback(const Graphics::ScreenshotInfo *info, love::image::ImageData *i, void *gd) { if (info == nullptr) @@ -700,30 +712,6 @@ static void parseDPIScale(Data *d, float *dpiscale) } } -static Image::Settings w__optImageSettings(lua_State *L, int idx, bool &setdpiscale) -{ - Image::Settings s; - - setdpiscale = false; - if (!lua_isnoneornil(L, idx)) - { - luax_checktablefields(L, idx, "image setting name", Image::getConstant); - - s.mipmaps = luax_boolflag(L, idx, Image::getConstant(Image::SETTING_MIPMAPS), s.mipmaps); - s.linear = luax_boolflag(L, idx, Image::getConstant(Image::SETTING_LINEAR), s.linear); - - lua_getfield(L, idx, Image::getConstant(Image::SETTING_DPI_SCALE)); - if (lua_isnumber(L, -1)) - { - s.dpiScale = (float) lua_tonumber(L, -1); - setdpiscale = true; - } - lua_pop(L, 1); - } - - return s; -} - static std::pair, StrongRef> getImageData(lua_State *L, int idx, bool allowcompressed, float *dpiscale) { @@ -757,283 +745,498 @@ getImageData(lua_State *L, int idx, bool allowcompressed, float *dpiscale) return std::make_pair(idata, cdata); } -static int w__pushNewImage(lua_State *L, Image::Slices &slices, const Image::Settings &settings) +static int w__pushNewTexture(lua_State *L, Texture::Slices *slices, const Texture::Settings &settings) { - StrongRef i; + StrongRef i; luax_catchexcept(L, - [&]() { i.set(instance()->newImage(slices, settings), Acquire::NORETAIN); }, - [&](bool) { slices.clear(); } + [&]() { i.set(instance()->newTexture(settings, slices), Acquire::NORETAIN); }, + [&](bool) { if (slices) slices->clear(); } ); luax_pushtype(L, i); return 1; } -int w_newCubeImage(lua_State *L) +static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool checkType, bool checkDimensions, OptionalBool forceRenderTarget, Texture::Settings &s, bool &setdpiscale) +{ + setdpiscale = false; + + if (opt && lua_isnoneornil(L, idx)) + return; + + luax_checktablefields(L, idx, "texture setting name", Texture::getConstant); + + if (forceRenderTarget.hasValue) + s.renderTarget = forceRenderTarget.value; + else + s.renderTarget = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_RENDER_TARGET), s.renderTarget); + + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_FORMAT)); + if (!lua_isnoneornil(L, -1)) + { + const char *str = luaL_checkstring(L, -1); + if (!getConstant(str, s.format)) + luax_enumerror(L, "pixel format", str); + } + lua_pop(L, 1); + + if (checkType) + { + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_TYPE)); + if (!lua_isnoneornil(L, -1)) + { + const char *str = luaL_checkstring(L, -1); + if (!Texture::getConstant(str, s.type)) + luax_enumerror(L, "texture type", Texture::getConstants(s.type), str); + } + lua_pop(L, 1); + } + + if (checkDimensions) + { + s.width = luax_checkintflag(L, idx, Texture::getConstant(Texture::SETTING_WIDTH)); + s.height = luax_checkintflag(L, idx, Texture::getConstant(Texture::SETTING_HEIGHT)); + if (s.type == TEXTURE_2D_ARRAY || s.type == TEXTURE_VOLUME) + s.layers = luax_checkintflag(L, idx, Texture::getConstant(Texture::SETTING_LAYERS)); + } + + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_MIPMAPS)); + if (!lua_isnoneornil(L, -1)) + { + if (lua_type(L, -1) == LUA_TBOOLEAN) + s.mipmaps = luax_toboolean(L, -1) ? Texture::MIPMAPS_MANUAL : Texture::MIPMAPS_NONE; + else + { + const char *str = luaL_checkstring(L, -1); + if (!Texture::getConstant(str, s.mipmaps)) + luax_enumerror(L, "Texture mipmap mode", Texture::getConstants(s.mipmaps), str); + } + } + lua_pop(L, 1); + + s.linear = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_LINEAR), s.linear); + s.msaa = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_MSAA), s.msaa); + + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_READABLE)); + if (!lua_isnoneornil(L, -1)) + s.readable.set(luax_checkboolean(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_DPI_SCALE)); + if (lua_isnumber(L, -1)) + { + s.dpiScale = (float) lua_tonumber(L, -1); + setdpiscale = true; + } + lua_pop(L, 1); +} + +int w_newRenderTarget(lua_State *L) { luax_checkgraphicscreated(L); - Image::Slices slices(TEXTURE_CUBE); + Texture::Settings s; - bool dpiscaleset = false; - Image::Settings settings = w__optImageSettings(L, 2, dpiscaleset); - float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; + OptionalBool forceRenderTarget(true); + bool setDPIScale = false; - auto imagemodule = Module::getInstance(Module::M_IMAGE); - - if (!lua_istable(L, 1)) + if (lua_istable(L, 1)) { - auto data = getImageData(L, 1, true, autodpiscale); - - std::vector> faces; - - if (data.first.get()) - { - luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(data.first); }); - - for (int i = 0; i < (int) faces.size(); i++) - slices.set(i, 0, faces[i]); - } - else - slices.add(data.second, 0, 0, true, settings.mipmaps); + luax_checktexturesettings(L, 1, false, true, true, forceRenderTarget, s, setDPIScale); } else { - int tlen = (int) luax_objlen(L, 1); + // check if width and height are given. else default to screen dimensions. + s.width = (int) luaL_optinteger(L, 1, instance()->getWidth()); + s.height = (int) luaL_optinteger(L, 2, instance()->getHeight()); - if (luax_isarrayoftables(L, 1)) + int startidx = 3; + + if (lua_isnumber(L, 3)) { - 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 ? autodpiscale : 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); - } - } + s.layers = (int) luaL_checkinteger(L, 3); + s.type = TEXTURE_2D_ARRAY; + startidx = 4; } - else - { - bool usemipmaps = false; - for (int i = 0; i < tlen; i++) + luax_checktexturesettings(L, startidx, true, true, false, forceRenderTarget, s, setDPIScale); + } + + // Default to the screen's current pixel density scale. + if (!setDPIScale) + s.dpiScale = instance()->getScreenDPIScale(); + + Texture *texture = nullptr; + luax_catchexcept(L, [&](){ texture = instance()->newTexture(s); }); + + luax_pushtype(L, texture); + texture->release(); + return 1; +} + +int w_newTexture(lua_State *L) +{ + luax_checkgraphicscreated(L); + + Texture::Slices slices(TEXTURE_2D); + Texture::Slices *slicesref = &slices; + + Texture::Settings settings; + settings.type = TEXTURE_2D; + bool dpiscaleset = false; + + if (lua_type(L, 1) == LUA_TNUMBER) + { + slicesref = nullptr; + + settings.width = (int) luaL_checkinteger(L, 1); + settings.height = (int) luaL_checkinteger(L, 2); + + int startidx = 3; + + if (lua_type(L, 3) == LUA_TNUMBER) + { + settings.layers = (int) luaL_checkinteger(L, 3); + settings.type = TEXTURE_2D_ARRAY; + startidx = 4; + } + + luax_checktexturesettings(L, startidx, true, true, false, OptionalBool(), settings, dpiscaleset); + } + else + { + luax_checktexturesettings(L, 2, true, false, false, OptionalBool(), settings, dpiscaleset); + float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; + + if (lua_istable(L, 1)) + { + int n = std::max(1, (int) luax_objlen(L, 1)); + for (int i = 0; i < n; i++) { lua_rawgeti(L, 1, i + 1); - auto data = getImageData(L, -1, true, i == 0 ? autodpiscale : 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); - } + slices.set(0, i, data.first); else - slices.add(data.second, i, 0, false, settings.mipmaps); + slices.set(0, i, data.second->getSlice(0, 0)); } + lua_pop(L, n); + } + else + { + auto data = getImageData(L, 1, true, autodpiscale); + if (data.first.get()) + slices.set(0, 0, data.first); + else + slices.add(data.second, 0, 0, false, settings.mipmaps != Texture::MIPMAPS_NONE); } - - lua_pop(L, tlen); } - return w__pushNewImage(L, slices, settings); + return w__pushNewTexture(L, slicesref, settings); } -int w_newArrayImage(lua_State *L) +int w_newCubeTexture(lua_State *L) { luax_checkgraphicscreated(L); - Image::Slices slices(TEXTURE_2D_ARRAY); + Texture::Slices slices(TEXTURE_CUBE); + Texture::Slices *slicesref = &slices; + Texture::Settings settings; + settings.type = TEXTURE_CUBE; bool dpiscaleset = false; - Image::Settings settings = w__optImageSettings(L, 2, dpiscaleset); - float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; - if (lua_istable(L, 1)) + if (lua_type(L, 1) == LUA_TNUMBER) { - 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 ? autodpiscale : 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 ? autodpiscale : nullptr); - if (data.first.get()) - slices.set(slice, 0, data.first); - else - slices.add(data.second, slice, 0, false, settings.mipmaps); - } - } - - lua_pop(L, tlen); + slicesref = nullptr; + settings.width = settings.height = (int) luaL_checkinteger(L, 1); + luax_checktexturesettings(L, 2, true, false, false, OptionalBool(), settings, dpiscaleset); } else { - auto data = getImageData(L, 1, true, autodpiscale); - if (data.first.get()) - slices.set(0, 0, data.first); + luax_checktexturesettings(L, 2, true, false, false, OptionalBool(), settings, dpiscaleset); + float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; + + auto imagemodule = Module::getInstance(Module::M_IMAGE); + + if (!lua_istable(L, 1)) + { + auto data = getImageData(L, 1, true, autodpiscale); + + std::vector> faces; + + if (data.first.get()) + { + luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(data.first); }); + + for (int i = 0; i < (int) faces.size(); i++) + slices.set(i, 0, faces[i]); + } + else + slices.add(data.second, 0, 0, true, settings.mipmaps != Texture::MIPMAPS_NONE); + } else - slices.add(data.second, 0, 0, true, settings.mipmaps); + { + 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 ? autodpiscale : 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 ? autodpiscale : 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, settings.mipmaps != Texture::MIPMAPS_NONE); + } + } + + lua_pop(L, tlen); + } } - return w__pushNewImage(L, slices, settings); + return w__pushNewTexture(L, slicesref, settings); } -int w_newVolumeImage(lua_State *L) +int w_newArrayTexture(lua_State *L) +{ + luax_checkgraphicscreated(L); + + Texture::Slices slices(TEXTURE_2D_ARRAY); + Texture::Slices *slicesref = &slices; + + Texture::Settings settings; + settings.type = TEXTURE_2D_ARRAY; + bool dpiscaleset = false; + + if (lua_type(L, 1) == LUA_TNUMBER) + { + slicesref = nullptr; + settings.width = (int) luaL_checkinteger(L, 1); + settings.height = (int) luaL_checkinteger(L, 2); + settings.layers = (int) luaL_checkinteger(L, 3); + luax_checktexturesettings(L, 4, true, false, false, OptionalBool(), settings, dpiscaleset); + } + else + { + luax_checktexturesettings(L, 2, true, false, false, OptionalBool(), settings, dpiscaleset); + float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; + + 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 ? autodpiscale : 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 ? autodpiscale : nullptr); + if (data.first.get()) + slices.set(slice, 0, data.first); + else + slices.add(data.second, slice, 0, false, settings.mipmaps != Texture::MIPMAPS_NONE); + } + } + + lua_pop(L, tlen); + } + else + { + auto data = getImageData(L, 1, true, autodpiscale); + if (data.first.get()) + slices.set(0, 0, data.first); + else + slices.add(data.second, 0, 0, true, settings.mipmaps != Texture::MIPMAPS_NONE); + } + } + + return w__pushNewTexture(L, slicesref, settings); +} + +int w_newVolumeTexture(lua_State *L) { luax_checkgraphicscreated(L); auto imagemodule = Module::getInstance(Module::M_IMAGE); - Image::Slices slices(TEXTURE_VOLUME); + Texture::Slices slices(TEXTURE_VOLUME); + Texture::Slices *slicesref = &slices; + Texture::Settings settings; + settings.type = TEXTURE_VOLUME; bool dpiscaleset = false; - Image::Settings settings = w__optImageSettings(L, 2, dpiscaleset); - float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; - if (lua_istable(L, 1)) + if (lua_type(L, 1) == LUA_TNUMBER) { - 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 ? autodpiscale : 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 ? autodpiscale : nullptr); - if (data.first.get()) - slices.set(layer, 0, data.first); - else - slices.add(data.second, layer, 0, false, settings.mipmaps); - } - } - - lua_pop(L, tlen); + slicesref = nullptr; + settings.width = (int) luaL_checkinteger(L, 1); + settings.height = (int) luaL_checkinteger(L, 2); + settings.layers = (int) luaL_checkinteger(L, 3); + luax_checktexturesettings(L, 4, true, false, false, OptionalBool(), settings, dpiscaleset); } else { - auto data = getImageData(L, 1, true, autodpiscale); + luax_checktexturesettings(L, 2, true, false, false, OptionalBool(), settings, dpiscaleset); + float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; - if (data.first.get()) + if (lua_istable(L, 1)) { - std::vector> layers; - luax_catchexcept(L, [&](){ layers = imagemodule->newVolumeLayers(data.first); }); + int tlen = std::max(1, (int) luax_objlen(L, 1)); - for (int i = 0; i < (int) layers.size(); i++) - slices.set(i, 0, layers[i]); + 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 ? autodpiscale : 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 ? autodpiscale : nullptr); + if (data.first.get()) + slices.set(layer, 0, data.first); + else + slices.add(data.second, layer, 0, false, settings.mipmaps != Texture::MIPMAPS_NONE); + } + } + + lua_pop(L, tlen); } else - slices.add(data.second, 0, 0, true, settings.mipmaps); + { + auto data = getImageData(L, 1, true, autodpiscale); + + if (data.first.get()) + { + std::vector> layers; + luax_catchexcept(L, [&](){ layers = imagemodule->newVolumeLayers(data.first); }); + + for (int i = 0; i < (int) layers.size(); i++) + slices.set(i, 0, layers[i]); + } + else + slices.add(data.second, 0, 0, true, settings.mipmaps != Texture::MIPMAPS_NONE); + } } - return w__pushNewImage(L, slices, settings); + return w__pushNewTexture(L, slicesref, settings); +} + +int w_newCanvas(lua_State *L) +{ + luax_markdeprecated(L, "love.graphics.newCanvas", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newRenderTarget"); + return w_newRenderTarget(L); } int w_newImage(lua_State *L) { - luax_checkgraphicscreated(L); + luax_markdeprecated(L, "love.graphics.newImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newTexture"); + return w_newTexture(L); +} - Image::Slices slices(TEXTURE_2D); +int w_newCubeImage(lua_State *L) +{ + luax_markdeprecated(L, "love.graphics.newCubeImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newCubeTexture"); + return w_newCubeTexture(L); +} - bool dpiscaleset = false; - Image::Settings settings = w__optImageSettings(L, 2, dpiscaleset); - float *autodpiscale = dpiscaleset ? nullptr : &settings.dpiScale; +int w_newArrayImage(lua_State *L) +{ + luax_markdeprecated(L, "love.graphics.newArrayImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newArrayTexture"); + return w_newArrayTexture(L); +} - if (lua_istable(L, 1)) - { - int n = std::max(1, (int) luax_objlen(L, 1)); - for (int i = 0; i < n; i++) - { - lua_rawgeti(L, 1, i + 1); - auto data = getImageData(L, -1, true, i == 0 ? autodpiscale : nullptr); - if (data.first.get()) - slices.set(0, i, data.first); - else - slices.set(0, i, data.second->getSlice(0, 0)); - } - lua_pop(L, n); - } - else - { - auto data = getImageData(L, 1, true, autodpiscale); - if (data.first.get()) - slices.set(0, 0, data.first); - else - slices.add(data.second, 0, 0, false, settings.mipmaps); - } - - return w__pushNewImage(L, slices, settings); +int w_newVolumeImage(lua_State *L) +{ + luax_markdeprecated(L, "love.graphics.newVolumeImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newVolumeTexture"); + return w_newVolumeTexture(L); } int w_newQuad(lua_State *L) @@ -1102,7 +1305,7 @@ int w_newFont(lua_State *L) love::font::Rasterizer *rasterizer = luax_checktype(L, 1); luax_catchexcept(L, [&]() { - font = instance()->newFont(rasterizer, instance()->getDefaultFilter()); } + font = instance()->newFont(rasterizer); } ); // Push the type. @@ -1115,9 +1318,6 @@ int w_newImageFont(lua_State *L) { luax_checkgraphicscreated(L); - // filter for glyphs - Texture::Filter filter = instance()->getDefaultFilter(); - // Convert to Rasterizer if necessary. if (!luax_istype(L, 1, love::font::Rasterizer::type)) { @@ -1133,7 +1333,7 @@ int w_newImageFont(lua_State *L) love::font::Rasterizer *rasterizer = luax_checktype(L, 1); // Create the font. - Font *font = instance()->newFont(rasterizer, filter); + Font *font = instance()->newFont(rasterizer); // Push the type. luax_pushtype(L, font); @@ -1184,79 +1384,6 @@ int w_newParticleSystem(lua_State *L) return 1; } -int w_newCanvas(lua_State *L) -{ - luax_checkgraphicscreated(L); - - Canvas::Settings settings; - - // check if width and height are given. else default to screen dimensions. - settings.width = (int) luaL_optinteger(L, 1, instance()->getWidth()); - settings.height = (int) luaL_optinteger(L, 2, instance()->getHeight()); - - // Default to the screen's current pixel density scale. - settings.dpiScale = instance()->getScreenDPIScale(); - - int startidx = 3; - - if (lua_isnumber(L, 3)) - { - settings.layers = (int) luaL_checkinteger(L, 3); - settings.type = TEXTURE_2D_ARRAY; - startidx = 4; - } - - if (!lua_isnoneornil(L, startidx)) - { - luax_checktablefields(L, startidx, "canvas setting name", Canvas::getConstant); - - settings.dpiScale = (float) luax_numberflag(L, startidx, Canvas::getConstant(Canvas::SETTING_DPI_SCALE), settings.dpiScale); - settings.msaa = luax_intflag(L, startidx, Canvas::getConstant(Canvas::SETTING_MSAA), settings.msaa); - - lua_getfield(L, startidx, Canvas::getConstant(Canvas::SETTING_FORMAT)); - if (!lua_isnoneornil(L, -1)) - { - const char *str = luaL_checkstring(L, -1); - if (!getConstant(str, settings.format)) - return luax_enumerror(L, "pixel format", str); - } - lua_pop(L, 1); - - lua_getfield(L, startidx, Canvas::getConstant(Canvas::SETTING_TYPE)); - if (!lua_isnoneornil(L, -1)) - { - const char *str = luaL_checkstring(L, -1); - if (!Texture::getConstant(str, settings.type)) - return luax_enumerror(L, "texture type", Texture::getConstants(settings.type), str); - } - lua_pop(L, 1); - - lua_getfield(L, startidx, Canvas::getConstant(Canvas::SETTING_READABLE)); - if (!lua_isnoneornil(L, -1)) - { - settings.readable.hasValue = true; - settings.readable.value = luax_checkboolean(L, -1); - } - lua_pop(L, 1); - - lua_getfield(L, startidx, Canvas::getConstant(Canvas::SETTING_MIPMAPS)); - if (!lua_isnoneornil(L, -1)) - { - const char *str = luaL_checkstring(L, -1); - if (!Canvas::getConstant(str, settings.mipmaps)) - return luax_enumerror(L, "Canvas mipmap mode", Canvas::getConstants(settings.mipmaps), str); - } - lua_pop(L, 1); - } - - Canvas *canvas = nullptr; - luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(settings); }); - - luax_pushtype(L, canvas); - canvas->release(); - return 1; -} - static int w_getShaderSource(lua_State *L, int startidx, bool gles, std::string &vertexsource, std::string &pixelsource) { using namespace love::filesystem; @@ -1935,69 +2062,65 @@ int w_getBlendState(lua_State *L) int w_setDefaultFilter(lua_State *L) { - Texture::Filter f; + SamplerState s = instance()->getDefaultSamplerState(); const char *minstr = luaL_checkstring(L, 1); const char *magstr = luaL_optstring(L, 2, minstr); - if (!Texture::getConstant(minstr, f.min)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr); - if (!Texture::getConstant(magstr, f.mag)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr); + if (!SamplerState::getConstant(minstr, s.minFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr); + if (!SamplerState::getConstant(magstr, s.magFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr); - f.anisotropy = (float) luaL_optnumber(L, 3, 1.0); - - instance()->setDefaultFilter(f); + s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 3, 1.0)), LOVE_UINT8_MAX); + instance()->setDefaultSamplerState(s); return 0; } int w_getDefaultFilter(lua_State *L) { - const Texture::Filter &f = instance()->getDefaultFilter(); + const SamplerState &s = instance()->getDefaultSamplerState(); const char *minstr; const char *magstr; - if (!Texture::getConstant(f.min, minstr)) + if (!SamplerState::getConstant(s.minFilter, minstr)) return luaL_error(L, "Unknown minification filter mode"); - if (!Texture::getConstant(f.mag, magstr)) + if (!SamplerState::getConstant(s.magFilter, magstr)) return luaL_error(L, "Unknown magnification filter mode"); lua_pushstring(L, minstr); lua_pushstring(L, magstr); - lua_pushnumber(L, f.anisotropy); + lua_pushnumber(L, s.maxAnisotropy); return 3; } int w_setDefaultMipmapFilter(lua_State *L) { - Texture::FilterMode filter = Texture::FILTER_NONE; + SamplerState s = instance()->getDefaultSamplerState(); + s.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; if (!lua_isnoneornil(L, 1)) { const char *str = luaL_checkstring(L, 1); - if (!Texture::getConstant(str, filter)) - return luax_enumerror(L, "filter mode", Texture::getConstants(filter), str); + if (!SamplerState::getConstant(str, s.mipmapFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.mipmapFilter), str); } - float sharpness = (float) luaL_optnumber(L, 2, 0); - - instance()->setDefaultMipmapFilter(filter, sharpness); + s.lodBias = -((float) luaL_optnumber(L, 2, 0.0)); + instance()->setDefaultSamplerState(s); return 0; } int w_getDefaultMipmapFilter(lua_State *L) { - Texture::FilterMode filter; - float sharpness; - - instance()->getDefaultMipmapFilter(&filter, &sharpness); + const SamplerState &s = instance()->getDefaultSamplerState(); const char *str; - if (Texture::getConstant(filter, str)) + if (SamplerState::getConstant(s.mipmapFilter, str)) lua_pushstring(L, str); else lua_pushnil(L); - lua_pushnumber(L, sharpness); + lua_pushnumber(L, -s.lodBias); return 2; } @@ -2248,6 +2371,45 @@ int w_getSupported(lua_State *L) return 1; } +int w_getTextureFormats(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + + bool rt = luax_checkboolflag(L, 1, Texture::getConstant(Texture::SETTING_RENDER_TARGET)); + bool linear = luax_boolflag(L, 1, Texture::getConstant(Texture::SETTING_LINEAR), false); + + OptionalBool readable; + lua_getfield(L, 1, Texture::getConstant(Texture::SETTING_READABLE)); + if (!lua_isnoneornil(L, -1)) + readable.set(luax_checkboolean(L, -1)); + lua_pop(L, 1); + + if (lua_istable(L, 2)) + lua_pushvalue(L, 2); + else + lua_createtable(L, 0, (int) PIXELFORMAT_MAX_ENUM); + + for (int i = 0; i < (int) PIXELFORMAT_MAX_ENUM; i++) + { + PixelFormat format = (PixelFormat) i; + const char *name = nullptr; + + if (format == PIXELFORMAT_UNKNOWN || !love::getConstant(format, name)) + continue; + + if (rt && isPixelFormatDepth(format)) + continue; + + bool formatReadable = readable.get(!isPixelFormatDepthStencil(format)); + bool sRGB = isGammaCorrect() && !linear; + + luax_pushboolean(L, instance()->isPixelFormatSupported(format, rt, formatReadable, sRGB)); + lua_setfield(L, -2, name); + } + + return 1; +} + static int w__getFormats(lua_State *L, int idx, bool (*isFormatSupported)(PixelFormat), bool (*ignore)(PixelFormat)) { if (lua_istable(L, idx)) @@ -2272,6 +2434,8 @@ static int w__getFormats(lua_State *L, int idx, bool (*isFormatSupported)(PixelF int w_getCanvasFormats(lua_State *L) { + luax_markdeprecated(L, "love.graphics.getCanvasFormats", API_FUNCTION, DEPRECATED_REPLACED, "love.graphics.getTextureFormats"); + bool (*supported)(PixelFormat); int idx = 1; @@ -2282,14 +2446,14 @@ int w_getCanvasFormats(lua_State *L) { supported = [](PixelFormat format) -> bool { - return instance()->isCanvasFormatSupported(format, true); + return instance()->isPixelFormatSupported(format, true, true, false); }; } else { supported = [](PixelFormat format) -> bool { - return instance()->isCanvasFormatSupported(format, false); + return instance()->isPixelFormatSupported(format, true, false, false); }; } } @@ -2297,7 +2461,8 @@ int w_getCanvasFormats(lua_State *L) { supported = [](PixelFormat format) -> bool { - return instance()->isCanvasFormatSupported(format); + bool readable = !isPixelFormatDepthStencil(format); + return instance()->isPixelFormatSupported(format, true, readable, false); }; } @@ -2306,9 +2471,11 @@ int w_getCanvasFormats(lua_State *L) int w_getImageFormats(lua_State *L) { + luax_markdeprecated(L, "love.graphics.getImageFormats", API_FUNCTION, DEPRECATED_REPLACED, "love.graphics.getTextureFormats"); + const auto supported = [](PixelFormat format) -> bool { - return instance()->isImageFormatSupported(format); + return instance()->isPixelFormatSupported(format, false, true, false); }; const auto ignore = [](PixelFormat format) -> bool @@ -2394,17 +2561,14 @@ int w_getStats(lua_State *L) lua_pushinteger(L, stats.drawCallsBatched); lua_setfield(L, -2, "drawcallsbatched"); - lua_pushinteger(L, stats.canvasSwitches); - lua_setfield(L, -2, "canvasswitches"); + lua_pushinteger(L, stats.renderTargetSwitches); + lua_setfield(L, -2, "rendertargetswitches"); lua_pushinteger(L, stats.shaderSwitches); lua_setfield(L, -2, "shaderswitches"); - lua_pushinteger(L, stats.canvases); - lua_setfield(L, -2, "canvases"); - - lua_pushinteger(L, stats.images); - lua_setfield(L, -2, "images"); + lua_pushinteger(L, stats.textures); + lua_setfield(L, -2, "textures"); lua_pushinteger(L, stats.fonts); lua_setfield(L, -2, "fonts"); @@ -2999,16 +3163,16 @@ static const luaL_Reg functions[] = { "discard", w_discard }, { "present", w_present }, - { "newImage", w_newImage }, - { "newArrayImage", w_newArrayImage }, - { "newVolumeImage", w_newVolumeImage }, - { "newCubeImage", w_newCubeImage }, + { "newRenderTarget", w_newRenderTarget }, + { "newTexture", w_newTexture }, + { "newCubeTexture", w_newCubeTexture }, + { "newArrayTexture", w_newArrayTexture }, + { "newVolumeTexture", w_newVolumeTexture }, { "newQuad", w_newQuad }, { "newFont", w_newFont }, { "newImageFont", w_newImageFont }, { "newSpriteBatch", w_newSpriteBatch }, { "newParticleSystem", w_newParticleSystem }, - { "newCanvas", w_newCanvas }, { "newShader", w_newShader }, { "newMesh", w_newMesh }, { "newText", w_newText }, @@ -3016,8 +3180,8 @@ static const luaL_Reg functions[] = { "validateShader", w_validateShader }, - { "setCanvas", w_setCanvas }, - { "getCanvas", w_getCanvas }, + { "setRenderTarget", w_setRenderTarget }, + { "getRenderTarget", w_getRenderTarget }, { "setColor", w_setColor }, { "getColor", w_getColor }, @@ -3060,8 +3224,7 @@ static const luaL_Reg functions[] = { "_setDefaultShaderCode", w_setDefaultShaderCode }, { "getSupported", w_getSupported }, - { "getCanvasFormats", w_getCanvasFormats }, - { "getImageFormats", w_getImageFormats }, + { "getTextureFormats", w_getTextureFormats }, { "getRendererInfo", w_getRendererInfo }, { "getSystemLimits", w_getSystemLimits }, { "getTextureTypes", w_getTextureTypes }, @@ -3118,6 +3281,17 @@ static const luaL_Reg functions[] = { "transformPoint", w_transformPoint }, { "inverseTransformPoint", w_inverseTransformPoint }, + // Deprecated + { "newCanvas", w_newCanvas }, + { "newImage", w_newImage }, + { "newArrayImage", w_newArrayImage }, + { "newVolumeImage", w_newVolumeImage }, + { "newCubeImage", w_newCubeImage }, + { "setCanvas", w_setCanvas }, + { "getCanvas", w_getCanvas }, + { "getCanvasFormats", w_getCanvasFormats }, + { "getImageFormats", w_getImageFormats }, + { 0, 0 } }; @@ -3132,11 +3306,9 @@ static const lua_CFunction types[] = luaopen_drawable, luaopen_texture, luaopen_font, - luaopen_image, luaopen_quad, luaopen_spritebatch, luaopen_particlesystem, - luaopen_canvas, luaopen_shader, luaopen_mesh, luaopen_text, diff --git a/src/modules/graphics/wrap_Graphics.h b/src/modules/graphics/wrap_Graphics.h index 7359ef652..afd4240ec 100644 --- a/src/modules/graphics/wrap_Graphics.h +++ b/src/modules/graphics/wrap_Graphics.h @@ -23,11 +23,10 @@ // LOVE #include "common/config.h" #include "wrap_Font.h" -#include "wrap_Image.h" +#include "wrap_Texture.h" #include "wrap_Quad.h" #include "wrap_SpriteBatch.h" #include "wrap_ParticleSystem.h" -#include "wrap_Canvas.h" #include "wrap_Shader.h" #include "wrap_Mesh.h" #include "wrap_Text.h" diff --git a/src/modules/graphics/wrap_GraphicsShader.lua b/src/modules/graphics/wrap_GraphicsShader.lua index 14a1ba19d..f0631c6f6 100644 --- a/src/modules/graphics/wrap_GraphicsShader.lua +++ b/src/modules/graphics/wrap_GraphicsShader.lua @@ -271,7 +271,7 @@ GLSL.PIXEL = { precision mediump float; #endif -#define love_MaxCanvases gl_MaxDrawBuffers +#define love_MaxRenderTargets gl_MaxDrawBuffers #if __VERSION__ >= 130 #define varying in @@ -279,19 +279,26 @@ GLSL.PIXEL = { // pixel shader outputs are defined, even when only one is actually used. // TODO: We should use reflection or something instead of this, to determine // how many outputs are actually used in the shader code. - #ifdef LOVE_MULTI_CANVAS - layout(location = 0) out vec4 love_Canvases[love_MaxCanvases]; - #define love_PixelColor love_Canvases[0] + #ifdef LOVE_MULTI_RENDER_TARGETS + layout(location = 0) out vec4 love_RenderTargets[love_MaxRenderTargets]; + #define love_PixelColor love_RenderTargets[0] #else layout(location = 0) out vec4 love_PixelColor; #endif #else - #ifdef LOVE_MULTI_CANVAS - #define love_Canvases gl_FragData + #ifdef LOVE_MULTI_RENDER_TARGETS + #define love_RenderTargets gl_FragData #endif #define love_PixelColor gl_FragColor #endif +// Legacy +#define love_MaxCanvases love_MaxRenderTargets +#define love_Canvases love_RenderTargets +#ifdef LOVE_MULTI_RENDER_TARGETS +#define LOVE_MULTI_CANVASES 1 +#endif + // See Shader::updateScreenParams in Shader.cpp. #define love_PixelCoord (vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenSize.z) + love_ScreenSize.w))]], @@ -345,14 +352,14 @@ local function getLanguageTarget(code) return (code:match("^%s*#pragma language (%w+)")) or "glsl1" end -local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacorrect, custom, multicanvas) +local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacorrect, custom, multirendertarget) stage = stage:upper() local lines = { GLSL.VERSION[lang][gles], "#define " ..stage .. " " .. 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 "", + multirendertarget and "#define LOVE_MULTI_RENDER_TARGETS 1" or "", GLSL.SYNTAX, GLSL[stage].HEADER, GLSL.UNIFORMS, @@ -373,8 +380,8 @@ local function isPixelCode(code) if code:match("vec4%s+effect%s*%(") then return true elseif code:match("void%s+effect%s*%(") then -- custom effect function - local multicanvas = code:match("love_Canvases") ~= nil - return true, true, multicanvas + local multirendertargets = (code:match("love_RenderTargets") ~= nil) or (code:match("love_Canvases") ~= nil) + return true, true, multirendertargets else return false end diff --git a/src/modules/graphics/wrap_Image.cpp b/src/modules/graphics/wrap_Image.cpp deleted file mode 100644 index 1d7da4773..000000000 --- a/src/modules/graphics/wrap_Image.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2006-2020 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 "wrap_Image.h" - -namespace love -{ -namespace graphics -{ - -Image *luax_checkimage(lua_State *L, int idx) -{ - return luax_checktype(L, idx); -} - -int w_Image_isFormatLinear(lua_State *L) -{ - Image *i = luax_checkimage(L, 1); - luax_pushboolean(L, i->isFormatLinear()); - return 1; -} - -int w_Image_isCompressed(lua_State *L) -{ - Image *i = luax_checkimage(L, 1); - luax_pushboolean(L, i->isCompressed()); - return 1; -} - -int w_Image_replacePixels(lua_State *L) -{ - Image *i = luax_checkimage(L, 1); - love::image::ImageData *id = luax_checktype(L, 2); - - int slice = 0; - int mipmap = 0; - int x = 0; - int y = 0; - bool reloadmipmaps = i->getMipmapsType() == Image::MIPMAPS_GENERATED; - - if (i->getTextureType() != TEXTURE_2D) - slice = (int) luaL_checkinteger(L, 3) - 1; - - mipmap = (int) luaL_optinteger(L, 4, 1) - 1; - - if (!lua_isnoneornil(L, 5)) - { - x = (int) luaL_checkinteger(L, 5); - y = (int) luaL_checkinteger(L, 6); - - if (reloadmipmaps) - reloadmipmaps = luax_optboolean(L, 7, reloadmipmaps); - } - - luax_catchexcept(L, [&](){ i->replacePixels(id, slice, mipmap, x, y, reloadmipmaps); }); - return 0; -} - -static const luaL_Reg w_Image_functions[] = -{ - { "isFormatLinear", w_Image_isFormatLinear }, - { "isCompressed", w_Image_isCompressed }, - { "replacePixels", w_Image_replacePixels }, - { 0, 0 } -}; - -extern "C" int luaopen_image(lua_State *L) -{ - return luax_register_type(L, &Image::type, w_Texture_functions, w_Image_functions, nullptr); -} - -} // graphics -} // love diff --git a/src/modules/graphics/wrap_Image.h b/src/modules/graphics/wrap_Image.h deleted file mode 100644 index ed3697bdd..000000000 --- a/src/modules/graphics/wrap_Image.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2006-2020 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. - **/ - -#pragma once - -// LOVE -#include "common/runtime.h" -#include "Image.h" -#include "wrap_Texture.h" - -namespace love -{ -namespace graphics -{ - -Image *luax_checkimage(lua_State *L, int idx); -extern "C" int luaopen_image(lua_State *L); - -} // graphics -} // love diff --git a/src/modules/graphics/wrap_Mesh.cpp b/src/modules/graphics/wrap_Mesh.cpp index 9e238dec1..3c9de2f5f 100644 --- a/src/modules/graphics/wrap_Mesh.cpp +++ b/src/modules/graphics/wrap_Mesh.cpp @@ -20,8 +20,7 @@ // LOVE #include "wrap_Mesh.h" -#include "Image.h" -#include "Canvas.h" +#include "Texture.h" #include "wrap_Texture.h" // C++ @@ -552,14 +551,7 @@ int w_Mesh_getTexture(lua_State *L) if (tex == nullptr) return 0; - // FIXME: big hack right here. - if (dynamic_cast(tex) != nullptr) - luax_pushtype(L, Image::type, tex); - else if (dynamic_cast(tex) != nullptr) - luax_pushtype(L, Canvas::type, tex); - else - return luaL_error(L, "Unable to determine texture type."); - + luax_pushtype(L, tex); return 1; } diff --git a/src/modules/graphics/wrap_ParticleSystem.cpp b/src/modules/graphics/wrap_ParticleSystem.cpp index 8c6c29dda..7458941fb 100644 --- a/src/modules/graphics/wrap_ParticleSystem.cpp +++ b/src/modules/graphics/wrap_ParticleSystem.cpp @@ -22,8 +22,7 @@ #include "wrap_ParticleSystem.h" #include "common/Vector.h" -#include "Image.h" -#include "Canvas.h" +#include "Texture.h" #include "wrap_Texture.h" // C @@ -62,16 +61,7 @@ int w_ParticleSystem_setTexture(lua_State *L) int w_ParticleSystem_getTexture(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); - Texture *tex = t->getTexture(); - - // FIXME: big hack right here. - if (dynamic_cast(tex) != nullptr) - luax_pushtype(L, Image::type, tex); - else if (dynamic_cast(tex) != nullptr) - luax_pushtype(L, Canvas::type, tex); - else - return luaL_error(L, "Unable to determine texture type."); - + luax_pushtype(L, t->getTexture()); return 1; } diff --git a/src/modules/graphics/wrap_SpriteBatch.cpp b/src/modules/graphics/wrap_SpriteBatch.cpp index 7986f042e..420ece908 100644 --- a/src/modules/graphics/wrap_SpriteBatch.cpp +++ b/src/modules/graphics/wrap_SpriteBatch.cpp @@ -20,8 +20,7 @@ // LOVE #include "wrap_SpriteBatch.h" -#include "Image.h" -#include "Canvas.h" +#include "Texture.h" #include "wrap_Texture.h" namespace love @@ -153,16 +152,7 @@ int w_SpriteBatch_setTexture(lua_State *L) int w_SpriteBatch_getTexture(lua_State *L) { SpriteBatch *t = luax_checkspritebatch(L, 1); - Texture *tex = t->getTexture(); - - // FIXME: big hack right here. - if (dynamic_cast(tex) != nullptr) - luax_pushtype(L, Image::type, tex); - else if (dynamic_cast(tex) != nullptr) - luax_pushtype(L, Canvas::type, tex); - else - return luaL_error(L, "Unable to determine texture type."); - + luax_pushtype(L, t->getTexture()); return 1; } diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index a41af87c1..28e1d6b5f 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -19,6 +19,7 @@ **/ #include "wrap_Texture.h" +#include "Graphics.h" namespace love { @@ -129,114 +130,135 @@ int w_Texture_getDPIScale(lua_State *L) return 1; } +int w_Texture_isFormatLinear(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_pushboolean(L, t->isFormatLinear()); + return 1; +} + +int w_Texture_isCompressed(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_pushboolean(L, t->isCompressed()); + return 1; +} + +int w_Texture_getMSAA(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + lua_pushinteger(L, t->getMSAA()); + return 1; +} + int w_Texture_setFilter(lua_State *L) { Texture *t = luax_checktexture(L, 1); - Texture::Filter f = t->getFilter(); + SamplerState s = t->getSamplerState(); const char *minstr = luaL_checkstring(L, 2); const char *magstr = luaL_optstring(L, 3, minstr); - if (!Texture::getConstant(minstr, f.min)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr); - if (!Texture::getConstant(magstr, f.mag)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr); + if (!SamplerState::getConstant(minstr, s.minFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr); + if (!SamplerState::getConstant(magstr, s.magFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr); - f.anisotropy = (float) luaL_optnumber(L, 4, 1.0); + s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX); - luax_catchexcept(L, [&](){ t->setFilter(f); }); + luax_catchexcept(L, [&](){ t->setSamplerState(s); }); return 0; } int w_Texture_getFilter(lua_State *L) { Texture *t = luax_checktexture(L, 1); - const Texture::Filter f = t->getFilter(); + const SamplerState &s = t->getSamplerState(); const char *minstr = nullptr; const char *magstr = nullptr; - if (!Texture::getConstant(f.min, minstr)) + if (!SamplerState::getConstant(s.minFilter, minstr)) return luaL_error(L, "Unknown filter mode."); - if (!Texture::getConstant(f.mag, magstr)) + if (!SamplerState::getConstant(s.magFilter, magstr)) return luaL_error(L, "Unknown filter mode."); lua_pushstring(L, minstr); lua_pushstring(L, magstr); - lua_pushnumber(L, f.anisotropy); + lua_pushnumber(L, s.maxAnisotropy); return 3; } int w_Texture_setMipmapFilter(lua_State *L) { Texture *t = luax_checktexture(L, 1); - Texture::Filter f = t->getFilter(); + SamplerState s = t->getSamplerState(); + // Mipmapping is disabled if no argument is given. if (lua_isnoneornil(L, 2)) - f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given + s.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; else { const char *mipmapstr = luaL_checkstring(L, 2); - if (!Texture::getConstant(mipmapstr, f.mipmap)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.mipmap), mipmapstr); + if (!SamplerState::getConstant(mipmapstr, s.mipmapFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.mipmapFilter), mipmapstr); } - luax_catchexcept(L, [&](){ t->setFilter(f); }); - t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0)); + s.lodBias = -((float) luaL_optnumber(L, 3, 0.0)); + luax_catchexcept(L, [&](){ t->setSamplerState(s); }); return 0; } int w_Texture_getMipmapFilter(lua_State *L) { Texture *t = luax_checktexture(L, 1); - - const Texture::Filter &f = t->getFilter(); + const SamplerState &s = t->getSamplerState(); const char *mipmapstr; - if (Texture::getConstant(f.mipmap, mipmapstr)) + if (SamplerState::getConstant(s.mipmapFilter, mipmapstr)) lua_pushstring(L, mipmapstr); else lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled - lua_pushnumber(L, t->getMipmapSharpness()); + lua_pushnumber(L, -s.lodBias); return 2; } int w_Texture_setWrap(lua_State *L) { Texture *t = luax_checktexture(L, 1); - Texture::Wrap w; + SamplerState s = t->getSamplerState(); const char *sstr = luaL_checkstring(L, 2); const char *tstr = luaL_optstring(L, 3, sstr); const char *rstr = luaL_optstring(L, 4, sstr); - if (!Texture::getConstant(sstr, w.s)) - return luax_enumerror(L, "wrap mode", Texture::getConstants(w.s), sstr); - if (!Texture::getConstant(tstr, w.t)) - return luax_enumerror(L, "wrap mode", Texture::getConstants(w.t), tstr); - if (!Texture::getConstant(rstr, w.r)) - return luax_enumerror(L, "wrap mode", Texture::getConstants(w.r), rstr); + if (!SamplerState::getConstant(sstr, s.wrapU)) + return luax_enumerror(L, "wrap mode", SamplerState::getConstants(s.wrapU), sstr); + if (!SamplerState::getConstant(tstr, s.wrapV)) + return luax_enumerror(L, "wrap mode", SamplerState::getConstants(s.wrapV), tstr); + if (!SamplerState::getConstant(rstr, s.wrapW)) + return luax_enumerror(L, "wrap mode", SamplerState::getConstants(s.wrapW), rstr); - luax_pushboolean(L, t->setWrap(w)); + luax_catchexcept(L, [&](){ t->setSamplerState(s); }); return 1; } int w_Texture_getWrap(lua_State *L) { Texture *t = luax_checktexture(L, 1); - const Texture::Wrap w = t->getWrap(); + const SamplerState &s = t->getSamplerState(); const char *sstr = nullptr; const char *tstr = nullptr; const char *rstr = nullptr; - if (!Texture::getConstant(w.s, sstr)) + if (!SamplerState::getConstant(s.wrapU, sstr)) return luaL_error(L, "Unknown wrap mode."); - if (!Texture::getConstant(w.t, tstr)) + if (!SamplerState::getConstant(s.wrapV, tstr)) return luaL_error(L, "Unknown wrap mode."); - if (!Texture::getConstant(w.r, rstr)) + if (!SamplerState::getConstant(s.wrapW, rstr)) return luaL_error(L, "Unknown wrap mode."); lua_pushstring(L, sstr); @@ -267,30 +289,31 @@ int w_Texture_isReadable(lua_State *L) int w_Texture_setDepthSampleMode(lua_State *L) { Texture *t = luax_checktexture(L, 1); + SamplerState s = t->getSamplerState(); - Optional mode; + s.depthSampleMode.hasValue = false; if (!lua_isnoneornil(L, 2)) { const char *str = luaL_checkstring(L, 2); - mode.hasValue = true; - if (!getConstant(str, mode.value)) - return luax_enumerror(L, "compare mode", getConstants(mode.value), str); + s.depthSampleMode.hasValue = true; + if (!getConstant(str, s.depthSampleMode.value)) + return luax_enumerror(L, "compare mode", getConstants(s.depthSampleMode.value), str); } - luax_catchexcept(L, [&]() { t->setDepthSampleMode(mode); }); + luax_catchexcept(L, [&](){ t->setSamplerState(s); }); return 0; } int w_Texture_getDepthSampleMode(lua_State *L) { Texture *t = luax_checktexture(L, 1); - Optional mode = t->getDepthSampleMode(); + const SamplerState &s = t->getSamplerState(); - if (mode.hasValue) + if (s.depthSampleMode.hasValue) { const char *str = nullptr; - if (!getConstant(mode.value, str)) + if (!getConstant(s.depthSampleMode.value, str)) return luaL_error(L, "Unknown compare mode."); lua_pushstring(L, str); } @@ -300,6 +323,139 @@ int w_Texture_getDepthSampleMode(lua_State *L) return 1; } +int w_Texture_getMipmapMode(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + const char *str; + if (!Texture::getConstant(t->getMipmapsMode(), str)) + return luax_enumerror(L, "mipmap mode", Texture::getConstants(Texture::MIPMAPS_MAX_ENUM), str); + lua_pushstring(L, str); + return 1; +} + +int w_Texture_generateMipmaps(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_catchexcept(L, [&]() { t->generateMipmaps(); }); + return 0; +} + +int w_Texture_replacePixels(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + love::image::ImageData *id = luax_checktype(L, 2); + + int slice = 0; + int mipmap = 0; + int x = 0; + int y = 0; + bool reloadmipmaps = t->getMipmapsMode() == Texture::MIPMAPS_AUTO; + + if (t->getTextureType() != TEXTURE_2D) + slice = (int) luaL_checkinteger(L, 3) - 1; + + mipmap = (int) luaL_optinteger(L, 4, 1) - 1; + + if (!lua_isnoneornil(L, 5)) + { + x = (int) luaL_checkinteger(L, 5); + y = (int) luaL_checkinteger(L, 6); + + if (reloadmipmaps) + reloadmipmaps = luax_optboolean(L, 7, reloadmipmaps); + } + + luax_catchexcept(L, [&](){ t->replacePixels(id, slice, mipmap, x, y, reloadmipmaps); }); + return 0; +} + +int w_Texture_newImageData(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + love::image::Image *image = luax_getmodule(L, love::image::Image::type); + + int slice = 0; + int mipmap = 0; + Rect rect = {0, 0, t->getPixelWidth(), t->getPixelHeight()}; + + if (t->getTextureType() != TEXTURE_2D) + slice = (int) luaL_checkinteger(L, 2) - 1; + + mipmap = (int) luaL_optinteger(L, 3, 1) - 1; + + if (!lua_isnoneornil(L, 4)) + { + rect.x = (int) luaL_checkinteger(L, 4); + rect.y = (int) luaL_checkinteger(L, 5); + rect.w = (int) luaL_checkinteger(L, 6); + rect.h = (int) luaL_checkinteger(L, 7); + } + + love::image::ImageData *img = nullptr; + luax_catchexcept(L, [&](){ img = t->newImageData(image, slice, mipmap, rect); }); + + luax_pushtype(L, img); + img->release(); + return 1; +} + +int w_Texture_renderTo(lua_State *L) +{ + Graphics::RenderTarget rt(luax_checktexture(L, 1)); + + int startidx = 2; + + if (rt.texture->getTextureType() != TEXTURE_2D) + { + rt.slice = (int) luaL_checkinteger(L, 2) - 1; + startidx++; + } + + luaL_checktype(L, startidx, LUA_TFUNCTION); + + auto graphics = Module::getInstance(Module::M_GRAPHICS); + + if (graphics) + { + // Save the current render targets so we can restore them when we're done. + Graphics::RenderTargets oldtargets = graphics->getRenderTargets(); + + for (auto c : oldtargets.colors) + c.texture->retain(); + + if (oldtargets.depthStencil.texture != nullptr) + oldtargets.depthStencil.texture->retain(); + + luax_catchexcept(L, + [&]() { graphics->setRenderTarget(rt, 0); }, + [&](bool err) + { + if (err) + { + for (auto c : oldtargets.colors) + c.texture->release(); + } + } + ); + + lua_settop(L, 2); // make sure the function is on top of the stack + int status = lua_pcall(L, 0, 0, 0); + + graphics->setRenderTargets(oldtargets); + + for (auto c : oldtargets.colors) + c.texture->release(); + + if (oldtargets.depthStencil.texture != nullptr) + oldtargets.depthStencil.texture->release(); + + if (status != 0) + return lua_error(L); + } + + return 0; +} + const luaL_Reg w_Texture_functions[] = { { "getTextureType", w_Texture_getTextureType }, @@ -313,6 +469,9 @@ const luaL_Reg w_Texture_functions[] = { "getPixelHeight", w_Texture_getPixelHeight }, { "getPixelDimensions", w_Texture_getPixelDimensions }, { "getDPIScale", w_Texture_getDPIScale }, + { "isFormatLinear", w_Texture_isFormatLinear }, + { "isCompressed", w_Texture_isCompressed }, + { "getMSAA", w_Texture_getMSAA }, { "setFilter", w_Texture_setFilter }, { "getFilter", w_Texture_getFilter }, { "setMipmapFilter", w_Texture_setMipmapFilter }, @@ -321,8 +480,13 @@ const luaL_Reg w_Texture_functions[] = { "getWrap", w_Texture_getWrap }, { "getFormat", w_Texture_getFormat }, { "isReadable", w_Texture_isReadable }, + { "getMipmapMode", w_Texture_getMipmapMode }, { "getDepthSampleMode", w_Texture_getDepthSampleMode }, { "setDepthSampleMode", w_Texture_setDepthSampleMode }, + { "generateMipmaps", w_Texture_generateMipmaps }, + { "replacePixels", w_Texture_replacePixels }, + { "newImageData", w_Texture_newImageData }, + { "renderTo", w_Texture_renderTo }, { 0, 0 } }; diff --git a/src/modules/graphics/wrap_Video.cpp b/src/modules/graphics/wrap_Video.cpp index 712330dbe..32d8b08ac 100644 --- a/src/modules/graphics/wrap_Video.cpp +++ b/src/modules/graphics/wrap_Video.cpp @@ -113,38 +113,38 @@ int w_Video_getPixelDimensions(lua_State *L) int w_Video_setFilter(lua_State *L) { Video *video = luax_checkvideo(L, 1); - Texture::Filter f = video->getFilter(); + SamplerState s = video->getSamplerState(); const char *minstr = luaL_checkstring(L, 2); const char *magstr = luaL_optstring(L, 3, minstr); - if (!Texture::getConstant(minstr, f.min)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr); - if (!Texture::getConstant(magstr, f.mag)) - return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr); + if (!SamplerState::getConstant(minstr, s.minFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr); + if (!SamplerState::getConstant(magstr, s.magFilter)) + return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr); - f.anisotropy = (float) luaL_optnumber(L, 4, 1.0); + s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX); - luax_catchexcept(L, [&](){ video->setFilter(f); }); + luax_catchexcept(L, [&](){ video->setSamplerState(s); }); return 0; } int w_Video_getFilter(lua_State *L) { Video *video = luax_checkvideo(L, 1); - const Texture::Filter f = video->getFilter(); + const SamplerState &s = video->getSamplerState(); const char *minstr = nullptr; const char *magstr = nullptr; - if (!Texture::getConstant(f.min, minstr)) + if (!SamplerState::getConstant(s.minFilter, minstr)) return luaL_error(L, "Unknown filter mode."); - if (!Texture::getConstant(f.mag, magstr)) + if (!SamplerState::getConstant(s.magFilter, magstr)) return luaL_error(L, "Unknown filter mode."); lua_pushstring(L, minstr); lua_pushstring(L, magstr); - lua_pushnumber(L, f.anisotropy); + lua_pushnumber(L, s.maxAnisotropy); return 3; } diff --git a/src/modules/image/ImageData.cpp b/src/modules/image/ImageData.cpp index d9238ba7c..41c89299c 100644 --- a/src/modules/image/ImageData.cpp +++ b/src/modules/image/ImageData.cpp @@ -84,7 +84,7 @@ love::image::ImageData *ImageData::clone() const void ImageData::create(int width, int height, PixelFormat format, void *data) { - size_t datasize = width * height * getPixelFormatSize(format); + size_t datasize = getPixelFormatSliceSize(format, width, height); try { @@ -140,7 +140,7 @@ void ImageData::decode(Data *data) throw love::Exception("Could not decode data to ImageData: unsupported encoded format"); } - if (decodedimage.size != decodedimage.width * decodedimage.height * getPixelFormatSize(decodedimage.format)) + if (decodedimage.size != getPixelFormatSliceSize(decodedimage.format, decodedimage.width, decodedimage.height)) { decoder->freeRawPixels(decodedimage.data); throw love::Exception("Could not convert image!"); @@ -782,7 +782,7 @@ love::thread::Mutex *ImageData::getMutex() const size_t ImageData::getPixelSize() const { - return getPixelFormatSize(format); + return getPixelFormatBlockSize(format); } bool ImageData::validPixelFormat(PixelFormat format) diff --git a/src/modules/image/magpie/EXRHandler.cpp b/src/modules/image/magpie/EXRHandler.cpp index 85cfde0fa..b37f692b1 100644 --- a/src/modules/image/magpie/EXRHandler.cpp +++ b/src/modules/image/magpie/EXRHandler.cpp @@ -193,7 +193,7 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data) throw love::Exception("Could not decode EXR image: unknown pixel format."); } - img.size = img.width * img.height * getPixelFormatSize(img.format); + img.size = getPixelFormatSliceSize(img.format, img.width, img.height); FreeEXRImage(&exrImage); diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 8f6af1f12..ec75492ea 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -465,8 +465,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) if (!graphics.get()) graphics.set(Module::getInstance(Module::M_GRAPHICS)); - if (graphics.get() && graphics->isCanvasActive()) - throw love::Exception("love.window.setMode cannot be called while a Canvas is active in love.graphics."); + if (graphics.get() && graphics->isRenderTargetActive()) + throw love::Exception("love.window.setMode cannot be called while a render target is active in love.graphics."); auto renderer = graphics->getRenderer(); @@ -702,8 +702,8 @@ void Window::close(bool allowExceptions) { if (graphics.get()) { - if (allowExceptions && graphics->isCanvasActive()) - throw love::Exception("love.window.close cannot be called while a Canvas is active in love.graphics."); + if (allowExceptions && graphics->isRenderTargetActive()) + throw love::Exception("love.window.close cannot be called while a render target is active in love.graphics."); graphics->unSetMode(); } @@ -740,8 +740,8 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) if (!window) return false; - if (graphics.get() && graphics->isCanvasActive()) - throw love::Exception("love.window.setFullscreen cannot be called while a Canvas is active in love.graphics."); + if (graphics.get() && graphics->isRenderTargetActive()) + throw love::Exception("love.window.setFullscreen cannot be called while a render target is active in love.graphics."); WindowSettings newsettings = settings; newsettings.fullscreen = fullscreen; @@ -979,7 +979,7 @@ bool Window::setIcon(love::image::ImageData *imgd) int w = imgd->getWidth(); int h = imgd->getHeight(); - int bytesperpixel = (int) getPixelFormatSize(imgd->getFormat()); + int bytesperpixel = (int) getPixelFormatBlockSize(imgd->getFormat()); int pitch = w * bytesperpixel; SDL_Surface *sdlicon = nullptr; diff --git a/src/scripts/nogame.lua b/src/scripts/nogame.lua index 6f721b2dc..f9603b9eb 100644 --- a/src/scripts/nogame.lua +++ b/src/scripts/nogame.lua @@ -3186,21 +3186,21 @@ function love.nogame() R.bg.cloud_3 = R.bg[dpiscale].cloud_3_png R.bg.cloud_4 = R.bg[dpiscale].cloud_4_png - img_duckloon_normal = love.graphics.newImage(R.duckloon.normal, settings) - img_duckloon_blink = love.graphics.newImage(R.duckloon.blink, settings) + img_duckloon_normal = love.graphics.newTexture(R.duckloon.normal, settings) + img_duckloon_blink = love.graphics.newTexture(R.duckloon.blink, settings) - img_n = love.graphics.newImage(R.chain.n, settings) - img_o = love.graphics.newImage(R.chain.o, settings) - img_g = love.graphics.newImage(R.chain.g, settings) - img_a = love.graphics.newImage(R.chain.a, settings) - img_m = love.graphics.newImage(R.chain.m, settings) - img_e = love.graphics.newImage(R.chain.e, settings) - img_square = love.graphics.newImage(R.chain.square, settings) + img_n = love.graphics.newTexture(R.chain.n, settings) + img_o = love.graphics.newTexture(R.chain.o, settings) + img_g = love.graphics.newTexture(R.chain.g, settings) + img_a = love.graphics.newTexture(R.chain.a, settings) + img_m = love.graphics.newTexture(R.chain.m, settings) + img_e = love.graphics.newTexture(R.chain.e, settings) + img_square = love.graphics.newTexture(R.chain.square, settings) - img_cloud_1 = love.graphics.newImage(R.bg.cloud_1, settings) - img_cloud_2 = love.graphics.newImage(R.bg.cloud_2, settings) - img_cloud_3 = love.graphics.newImage(R.bg.cloud_3, settings) - img_cloud_4 = love.graphics.newImage(R.bg.cloud_4, settings) + img_cloud_1 = love.graphics.newTexture(R.bg.cloud_1, settings) + img_cloud_2 = love.graphics.newTexture(R.bg.cloud_2, settings) + img_cloud_3 = love.graphics.newTexture(R.bg.cloud_3, settings) + img_cloud_4 = love.graphics.newTexture(R.bg.cloud_4, settings) cloud_images = { img_cloud_1, diff --git a/src/scripts/nogame.lua.h b/src/scripts/nogame.lua.h index be9da60e1..d3e51767f 100644 --- a/src/scripts/nogame.lua.h +++ b/src/scripts/nogame.lua.h @@ -11936,54 +11936,60 @@ const unsigned char nogame_lua[] = 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x64, 0x75, 0x63, 0x6b, 0x6c, 0x6f, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, - 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x64, 0x75, 0x63, 0x6b, 0x6c, - 0x6f, 0x6f, 0x6e, 0x2e, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x29, 0x0a, + 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x64, 0x75, 0x63, + 0x6b, 0x6c, 0x6f, 0x6f, 0x6e, 0x2e, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x64, 0x75, 0x63, 0x6b, 0x6c, 0x6f, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x69, 0x6e, 0x6b, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, - 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x64, 0x75, 0x63, 0x6b, 0x6c, 0x6f, - 0x6f, 0x6e, 0x2e, 0x62, 0x6c, 0x69, 0x6e, 0x6b, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x29, 0x0a, + 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x64, 0x75, 0x63, 0x6b, + 0x6c, 0x6f, 0x6f, 0x6e, 0x2e, 0x62, 0x6c, 0x69, 0x6e, 0x6b, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x29, 0x0a, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x6e, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x6e, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, + 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x6e, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x6f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x6f, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, + 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x6f, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x67, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x67, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, + 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x67, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x61, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x61, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, + 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x61, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x6d, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x6d, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, + 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x6d, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x65, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x52, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, + 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, - 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, - 0x65, 0x28, 0x52, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x2c, 0x20, - 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, + 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, + 0x2c, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x6c, 0x6f, - 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x31, 0x2c, 0x20, 0x73, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x31, 0x2c, + 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x32, 0x20, 0x3d, 0x20, 0x6c, 0x6f, - 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x32, 0x2c, 0x20, 0x73, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x32, 0x2c, + 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x33, 0x20, 0x3d, 0x20, 0x6c, 0x6f, - 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x33, 0x2c, 0x20, 0x73, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x33, 0x2c, + 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x34, 0x20, 0x3d, 0x20, 0x6c, 0x6f, - 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x34, 0x2c, 0x20, 0x73, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, + 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x28, 0x52, 0x2e, 0x62, 0x67, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x34, 0x2c, + 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x0a, 0x0a, 0x09, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x69, 0x6d, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x31, 0x2c, 0x0a,