From 83f86eac64f0df8abaf07b167c240d0eff184844 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Thu, 14 Jun 2012 15:26:55 -0300 Subject: [PATCH] experimental map shaders --- modules/game/game.otmod | 2 +- modules/game_shaders/images/clouds.png | Bin 0 -> 69251 bytes modules/game_shaders/map.frag | 105 --------------- modules/game_shaders/shaders.lua | 56 ++++++++ modules/game_shaders/shaders.otmod | 15 +++ modules/game_shaders/shaders.otui | 15 +++ modules/game_shaders/shaders/bloom.frag | 16 +++ modules/game_shaders/shaders/default.frag | 8 ++ modules/game_shaders/shaders/fog.frag | 18 +++ modules/game_shaders/shaders/grayscale.frag | 14 ++ modules/game_shaders/shaders/oldtv.frag | 24 ++++ modules/game_shaders/shaders/party.frag | 13 ++ modules/game_shaders/shaders/pulse.frag | 19 +++ modules/game_shaders/shaders/radialblur.frag | 46 +++++++ modules/game_shaders/shaders/sepia.frag | 16 +++ modules/game_shaders/shaders/zomg.frag | 13 ++ src/framework/CMakeLists.txt | 15 ++- src/framework/application.cpp | 6 +- src/framework/graphics/font.cpp | 2 +- src/framework/graphics/framebuffer.cpp | 11 +- src/framework/graphics/graphics.cpp | 39 +----- src/framework/graphics/graphics.h | 2 - src/framework/graphics/painter.cpp | 40 +++++- src/framework/graphics/painter.h | 7 + src/framework/graphics/painterogl1.h | 2 + src/framework/graphics/painterogl2.cpp | 38 ++++-- src/framework/graphics/painterogl2.h | 4 + .../graphics/painterogl2_shadersources.h | 30 ++--- .../graphics/paintershadermanager.cpp | 76 ----------- .../graphics/paintershaderprogram.cpp | 82 +++++++++--- src/framework/graphics/paintershaderprogram.h | 11 +- src/framework/graphics/shader.cpp | 8 +- src/framework/graphics/shaderprogram.h | 4 +- src/framework/graphics/texture.cpp | 18 ++- src/framework/graphics/texture.h | 3 + src/framework/luafunctions.cpp | 4 + src/otclient/CMakeLists.txt | 4 +- src/otclient/core/declarations.h | 2 + src/otclient/core/gameshadermanager.cpp | 24 ---- src/otclient/core/gameshadermanager.h | 30 ----- src/otclient/core/item.cpp | 31 ++--- src/otclient/core/item.h | 1 + src/otclient/core/mapview.cpp | 10 +- src/otclient/core/mapview.h | 5 +- src/otclient/core/shadermanager.cpp | 124 ++++++++++++++++++ src/otclient/core/shadermanager.h | 62 +++++++++ src/otclient/luafunctions.cpp | 13 ++ src/otclient/otclient.cpp | 9 ++ src/otclient/otclient.h | 1 + src/otclient/ui/uimap.h | 2 + 50 files changed, 724 insertions(+), 376 deletions(-) create mode 100644 modules/game_shaders/images/clouds.png delete mode 100644 modules/game_shaders/map.frag create mode 100644 modules/game_shaders/shaders.lua create mode 100644 modules/game_shaders/shaders.otmod create mode 100644 modules/game_shaders/shaders.otui create mode 100644 modules/game_shaders/shaders/bloom.frag create mode 100644 modules/game_shaders/shaders/default.frag create mode 100644 modules/game_shaders/shaders/fog.frag create mode 100644 modules/game_shaders/shaders/grayscale.frag create mode 100644 modules/game_shaders/shaders/oldtv.frag create mode 100644 modules/game_shaders/shaders/party.frag create mode 100644 modules/game_shaders/shaders/pulse.frag create mode 100644 modules/game_shaders/shaders/radialblur.frag create mode 100644 modules/game_shaders/shaders/sepia.frag create mode 100644 modules/game_shaders/shaders/zomg.frag delete mode 100644 src/framework/graphics/paintershadermanager.cpp delete mode 100644 src/otclient/core/gameshadermanager.cpp delete mode 100644 src/otclient/core/gameshadermanager.h create mode 100644 src/otclient/core/shadermanager.cpp create mode 100644 src/otclient/core/shadermanager.h diff --git a/modules/game/game.otmod b/modules/game/game.otmod index 7771b880..40577848 100644 --- a/modules/game/game.otmod +++ b/modules/game/game.otmod @@ -8,7 +8,6 @@ Module - client_extended - game_tibiafiles - client_background - //- game_shaders load-later: - game_textmessage @@ -29,6 +28,7 @@ Module - game_questlog - game_ruleviolation - game_bugreport + - game_shaders @onLoad: | importStyle 'styles/items.otui' diff --git a/modules/game_shaders/images/clouds.png b/modules/game_shaders/images/clouds.png new file mode 100644 index 0000000000000000000000000000000000000000..66f8fb3437c8092222eacb821c247287d9fe001e GIT binary patch literal 69251 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 z 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 RBnSWi002ovPDHLkV1g_D7kmH! literal 0 HcmV?d00001 diff --git a/modules/game_shaders/map.frag b/modules/game_shaders/map.frag deleted file mode 100644 index b13b0695..00000000 --- a/modules/game_shaders/map.frag +++ /dev/null @@ -1,105 +0,0 @@ -uniform float opacity; // painter opacity -uniform vec4 color; // painter color -uniform float time; // time in seconds since shader linkage -uniform sampler2D tex0; // map texture -varying vec2 texCoord; // map texture coords -//uniform vec4 awareArea; - - -void main() -{ - gl_FragColor = texture2D(tex0, texCoord); -} - -/* -vec4 grayScale(vec4 color, float factor) -{ - float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); - return vec4(gray*factor + (1-factor)*color.r, - gray*factor + (1-factor)*color.g, - gray*factor + (1-factor)*color.b, 1); -} - -// grayscale fog -void main() -{ - vec4 color = texture2D(texture, textureCoords); - float dist = 0; - // left - if(textureCoords.x < awareArea.x && textureCoords.y < awareArea.y) - dist = distance(textureCoords, awareArea.xy); - else if(textureCoords.x < awareArea.x && textureCoords.y < awareArea.w) - dist = distance(textureCoords, vec2(awareArea.x, textureCoords.y)); - else if(textureCoords.x < awareArea.x) - dist = distance(textureCoords, awareArea.xw); - // right - else if(textureCoords.x > awareArea.z && textureCoords.y < awareArea.y) - dist = distance(textureCoords, awareArea.zy); - else if(textureCoords.x > awareArea.z && textureCoords.y < awareArea.w) - dist = distance(textureCoords, vec2(awareArea.z, textureCoords.y)); - else if(textureCoords.x > awareArea.z) - dist = distance(textureCoords, awareArea.zw); - // top - else if(textureCoords.y < awareArea.y) - dist = distance(textureCoords, vec2(textureCoords.x, awareArea.y)); - // bottom - else if(textureCoords.y > awareArea.w) - dist = distance(textureCoords, vec2(textureCoords.x, awareArea.w)); - if(dist > 0) { - float range = 0.01; - float factor = min(dist/range, 1.0); - color = grayScale(color, factor); - //color.rgb *= 1 - (factor * 0.5); - } - gl_FragColor = color; -} -*/ - -/* -sepia -void main() -{ - vec4 color = texture2D(texture, textureCoords); - if(textureCoords.x < awareArea.x || textureCoords.y < awareArea.y || textureCoords.x > awareArea.z || textureCoords.y > awareArea.w) { - gl_FragColor.r = dot(color, vec4(.393, .769, .189, .0)); - gl_FragColor.g = dot(color, vec4(.349, .686, .168, .0)); - gl_FragColor.b = dot(color, vec4(.272, .534, .131, .0)); - gl_FragColor.a = 1; - } else - gl_FragColor = color; -} -*/ - -/* -void main() -{ - vec4 color = texture2D(texture, textureCoords); - vec2 awareRange = (awareArea.zw - awareArea.xy)/2.0; - float dist = distance(textureCoords, awareArea.xy + awareRange); - float start = min(awareRange.x, awareRange.y); - float range = awareRange*0.1; - float endFactor = 0.3; - if(dist >= start) { - color.rgb *= 1 - (min((dist - start)/range, 1.0) * (2.0 - endFactor)); - } - gl_FragColor = color; -} -*/ -/* -void main() -{ - vec4 sum = vec4(0); - vec2 texcoord = texCoord; - int j; - int i; - - for( i= -4 ;i < 4; i++) - { - for (j = -4; j < 4; j++) - { - sum += texture2D(tex0, texcoord + vec2(j, i)*0.0025) * 0.0125; - } - } - gl_FragColor = texture2D(tex0, texCoord) + sum; -} -*/ \ No newline at end of file diff --git a/modules/game_shaders/shaders.lua b/modules/game_shaders/shaders.lua new file mode 100644 index 00000000..99536f12 --- /dev/null +++ b/modules/game_shaders/shaders.lua @@ -0,0 +1,56 @@ +Shaders = {} + +local HOTKEY = 'Ctrl+X' +local SHADERS = { + ['Default'] = 'shaders/default.frag', + ['Bloom'] = 'shaders/bloom.frag', + ['Sepia'] = 'shaders/sepia.frag', + ['Grayscale'] = 'shaders/grayscale.frag', + ['Pulse'] = 'shaders/pulse.frag', + ['Old Tv'] = 'shaders/oldtv.frag', + ['Fog'] = 'shaders/fog.frag', + ['Party'] = 'shaders/party.frag', + ['Radial Blur'] = 'shaders/radialblur.frag', + ['Zomg'] = 'shaders/zomg.frag', +} + +local shadersPanel + +function Shaders.init() + importStyle 'shaders.otui' + + + Keyboard.bindKeyDown(HOTKEY, Shaders.toggle) + + shadersPanel = createWidget('ShadersPanel', GameInterface.getMapPanel()) + shadersPanel:hide() + + local mapComboBox = shadersPanel:getChildById('mapComboBox') + mapComboBox.onOptionChange = function(combobox, option) + local map = GameInterface.getMapPanel() + map:setMapShader(g_shaders.getShader(option)) + print(option) + end + + for name,file in pairs(SHADERS) do + local shader = g_shaders.createFragmentShader(name, file) + mapComboBox:addOption(name) + + if name == 'Fog' then + shader:addMultiTexture('images/clouds.png') + end + end + + local map = GameInterface.getMapPanel() + map:setMapShader(g_shaders.getShader('Default')) +end + +function Shaders.terminate() + Keyboard.unbindKeyDown(HOTKEY) + shadersPanel:destroy() + shadersPanel = nil +end + +function Shaders.toggle() + shadersPanel:setVisible(not shadersPanel:isVisible()) +end diff --git a/modules/game_shaders/shaders.otmod b/modules/game_shaders/shaders.otmod new file mode 100644 index 00000000..f63e3ba6 --- /dev/null +++ b/modules/game_shaders/shaders.otmod @@ -0,0 +1,15 @@ +Module + name: game_shaders + description: Manage game shaders + author: edubart + website: www.otclient.info + + dependencies: + - game + + @onLoad: | + dofile 'shaders' + Shaders.init() + + @onUnload: | + Shaders.terminate() diff --git a/modules/game_shaders/shaders.otui b/modules/game_shaders/shaders.otui new file mode 100644 index 00000000..27cecdb5 --- /dev/null +++ b/modules/game_shaders/shaders.otui @@ -0,0 +1,15 @@ +ShadersPanel < UIWidget + focusable: false + + anchors.top: parent.top + anchors.left: parent.left + border-color: red + border-width: 1 + + width: 100 + layout: + type: verticalBox + fit-children: true + + ComboBox + id: mapComboBox \ No newline at end of file diff --git a/modules/game_shaders/shaders/bloom.frag b/modules/game_shaders/shaders/bloom.frag new file mode 100644 index 00000000..55abbc00 --- /dev/null +++ b/modules/game_shaders/shaders/bloom.frag @@ -0,0 +1,16 @@ +uniform float u_Time; +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +void main() +{ + vec4 color = texture2D(u_Tex0, v_TexCoord); + int j; + int i; + + for(i = -2 ;i <= 2; i++) + for(j = -2; j <= 2; j++) + color += texture2D(u_Tex0, v_TexCoord + vec2(i, j)*0.0025) * 0.025; + + gl_FragColor = color; +} diff --git a/modules/game_shaders/shaders/default.frag b/modules/game_shaders/shaders/default.frag new file mode 100644 index 00000000..b54ca882 --- /dev/null +++ b/modules/game_shaders/shaders/default.frag @@ -0,0 +1,8 @@ +uniform float u_Time; +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord); +} diff --git a/modules/game_shaders/shaders/fog.frag b/modules/game_shaders/shaders/fog.frag new file mode 100644 index 00000000..da1fe4ca --- /dev/null +++ b/modules/game_shaders/shaders/fog.frag @@ -0,0 +1,18 @@ +uniform float u_Time; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; +varying vec2 v_TexCoord; + +vec2 direction = vec2(1.0,0.3); +float speed = 0.05; +float pressure = 0.6; +float zoom = 0.5; + +void main(void) +{ + vec2 offset = (v_TexCoord + (direction * u_Time * speed)) / zoom; + vec3 bgcol = texture2D(u_Tex0, v_TexCoord).xyz; + vec3 fogcol = texture2D(u_Tex1, offset).xyz; + vec3 col = bgcol + fogcol*pressure; + gl_FragColor = vec4(col,1.0); +} diff --git a/modules/game_shaders/shaders/grayscale.frag b/modules/game_shaders/shaders/grayscale.frag new file mode 100644 index 00000000..00ad1e75 --- /dev/null +++ b/modules/game_shaders/shaders/grayscale.frag @@ -0,0 +1,14 @@ +uniform float u_Time; +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +vec4 grayscale(vec4 color) +{ + float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); + return vec4(gray, gray, gray, 1); +} + +void main() +{ + gl_FragColor = grayscale(texture2D(u_Tex0, v_TexCoord)); +} diff --git a/modules/game_shaders/shaders/oldtv.frag b/modules/game_shaders/shaders/oldtv.frag new file mode 100644 index 00000000..ea470c0f --- /dev/null +++ b/modules/game_shaders/shaders/oldtv.frag @@ -0,0 +1,24 @@ +uniform float u_Time; +varying vec2 v_TexCoord; +uniform sampler2D u_Tex0; + +void main(void) +{ + vec2 q = v_TexCoord; + vec2 uv = 0.5 + (q-0.5)*(0.9 + 0.1*sin(0.2*u_Time)); + + vec3 oricol = texture2D(u_Tex0,vec2(q.x,q.y)).xyz; + vec3 col = oricol; + + col = clamp(col*0.5+0.5*col*col*1.2,0.0,1.0); + + col *= 0.5 + 0.5*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y); + + col *= vec3(0.8,1.0,0.7); + + col *= 0.9+0.1*sin(10.0*u_Time+uv.y*1000.0); + + col *= 0.97+0.03*sin(110.0*u_Time); + + gl_FragColor = vec4(col,1.0); +} diff --git a/modules/game_shaders/shaders/party.frag b/modules/game_shaders/shaders/party.frag new file mode 100644 index 00000000..a3e801a3 --- /dev/null +++ b/modules/game_shaders/shaders/party.frag @@ -0,0 +1,13 @@ +uniform float u_Time; +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +void main() +{ + vec4 col = texture2D(u_Tex0, v_TexCoord); + float d = u_Time * 2; + col.x += (1.0 + sin(d))*0.25; + col.y += (1.0 + sin(d*2))*0.25; + col.z += (1.0 + sin(d*4))*0.25; + gl_FragColor = col; +} diff --git a/modules/game_shaders/shaders/pulse.frag b/modules/game_shaders/shaders/pulse.frag new file mode 100644 index 00000000..bb4dc60d --- /dev/null +++ b/modules/game_shaders/shaders/pulse.frag @@ -0,0 +1,19 @@ +uniform float u_Time; +uniform vec2 u_Resolution; +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +void main(void) +{ + vec2 halfres = u_Resolution.xy/2.0; + vec2 cPos = v_TexCoord.xy * u_Resolution; + + cPos.x -= 0.5*halfres.x*sin(u_Time/2.0)+0.3*halfres.x*cos(u_Time)+halfres.x; + cPos.y -= 0.4*halfres.y*sin(u_Time/5.0)+0.3*halfres.y*cos(u_Time)+halfres.y; + float cLength = length(cPos); + + vec2 uv = v_TexCoord.xy+ ((cPos/cLength)*sin(cLength/30.0-u_Time*10.0)/25.0)*0.15; + vec3 col = texture2D(u_Tex0,uv).xyz * 250.0/cLength; + + gl_FragColor = vec4(col,1.0); +} \ No newline at end of file diff --git a/modules/game_shaders/shaders/radialblur.frag b/modules/game_shaders/shaders/radialblur.frag new file mode 100644 index 00000000..b07301bc --- /dev/null +++ b/modules/game_shaders/shaders/radialblur.frag @@ -0,0 +1,46 @@ +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +// some const, tweak for best look +const float sampleDist = 1.0; +const float sampleStrength = 2.2; + +void main(void) +{ + // some sample positions + float samples[10] = + float[](-0.08,-0.05,-0.03,-0.02,-0.01,0.01,0.02,0.03,0.05,0.08); + + // 0.5,0.5 is the center of the screen + // so substracting v_TexCoord from it will result in + // a vector pointing to the middle of the screen + vec2 dir = 0.5 - v_TexCoord; + + // calculate the distance to the center of the screen + float dist = sqrt(dir.x*dir.x + dir.y*dir.y); + + // normalize the direction (reuse the distance) + dir = dir/dist; + + // this is the original colour of this fragment + // using only this would result in a nonblurred version + vec4 color = texture2D(u_Tex0,v_TexCoord); + + vec4 sum = color; + + // take 10 additional blur samples in the direction towards + // the center of the screen + for(int i = 0; i < 10; i++) + sum += texture2D(u_Tex0, v_TexCoord + dir * samples[i] * sampleDist); + + // we have taken eleven samples + sum *= 1.0/11.0; + + // weighten the blur effect with the distance to the + // center of the screen ( further out is blurred more) + float t = dist * sampleStrength; + t = clamp(t ,0.0,1.0); //0 <= t <= 1 + + //Blend the original color with the averaged pixels + gl_FragColor = mix(color, sum, t); +} \ No newline at end of file diff --git a/modules/game_shaders/shaders/sepia.frag b/modules/game_shaders/shaders/sepia.frag new file mode 100644 index 00000000..a20dd6d8 --- /dev/null +++ b/modules/game_shaders/shaders/sepia.frag @@ -0,0 +1,16 @@ +uniform float u_Time; +uniform sampler2D u_Tex0; +varying vec2 v_TexCoord; + +vec4 sepia(vec4 color) +{ + return vec4(dot(color, vec4(.393, .769, .189, .0)), + dot(color, vec4(.349, .686, .168, .0)), + dot(color, vec4(.272, .534, .131, .0)), + 1); +} + +void main() +{ + gl_FragColor = sepia(texture2D(u_Tex0, v_TexCoord)); +} \ No newline at end of file diff --git a/modules/game_shaders/shaders/zomg.frag b/modules/game_shaders/shaders/zomg.frag new file mode 100644 index 00000000..8285021b --- /dev/null +++ b/modules/game_shaders/shaders/zomg.frag @@ -0,0 +1,13 @@ +uniform sampler2D u_Tex0; +uniform float u_Time; +varying vec2 v_TexCoord; + +vec2 tibiaDir = vec2(1.0, 1.0); +void main(void) +{ + vec2 dir = 0.5 - v_TexCoord; + float dist = sqrt(dir.x*dir.x + dir.y*dir.y); + float scale = 0.8 + dist*0.5; + vec4 color = texture2D(u_Tex0, -(dir*scale - 0.5)); + gl_FragColor = color; +} diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index d6579ddb..fa25364a 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -16,12 +16,17 @@ IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE RelWithDebInfo) ENDIF() -# setup compiler options -SET(CXX_WARNS "-Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-but-set-variable") -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_WARNS} -std=gnu++0x -pipe") +IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(ARCH_FLAGS "-m64 -march=x86-64 -mtune=generic") +ELSE() + SET(ARCH_FLAGS "-m32 -march=i686 -mtune=generic") +ENDIF() +SET(WARNS_FLAGS "-Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-but-set-variable") +SET(SECURE_FLAGS "-fstack-protector --param=ssp-buffer-size=4 -D_FORTIFY_SOURCE=2") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_WARNS} ${SECURE_FLAGS} ${ARCH_FLAGS} -std=gnu++0x -pipe") SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -ggdb") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O1 -ggdb -fno-omit-frame-pointer") -SET(CMAKE_CXX_FLAGS_RELEASE "-O3") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2") SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -static-libgcc -static-libstdc++ -Wl,--as-needed") @@ -222,8 +227,6 @@ SET(framework_SOURCES ${framework_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/graphics/painterogl2.cpp ${CMAKE_CURRENT_LIST_DIR}/graphics/painterogl2.h ${CMAKE_CURRENT_LIST_DIR}/graphics/painterogl2_shadersources.h - ${CMAKE_CURRENT_LIST_DIR}/graphics/paintershadermanager.cpp - ${CMAKE_CURRENT_LIST_DIR}/graphics/paintershadermanager.h ${CMAKE_CURRENT_LIST_DIR}/graphics/paintershaderprogram.cpp ${CMAKE_CURRENT_LIST_DIR}/graphics/paintershaderprogram.h ${CMAKE_CURRENT_LIST_DIR}/graphics/particleaffector.cpp diff --git a/src/framework/application.cpp b/src/framework/application.cpp index b748197f..07c16bcf 100644 --- a/src/framework/application.cpp +++ b/src/framework/application.cpp @@ -201,10 +201,8 @@ void Application::run() } if(redraw) { - g_graphics.beginRender(); - if(cacheForeground) { - Rect viewportRect(0, 0, g_graphics.getViewportSize()); + Rect viewportRect(0, 0, g_painter->getResolution()); // draw the foreground into a texture if(updateForeground) { @@ -236,8 +234,6 @@ void Application::run() g_ui.render(Fw::BothPanes); } - g_graphics.endRender(); - // update screen pixels g_window.swapBuffers(); } diff --git a/src/framework/graphics/font.cpp b/src/framework/graphics/font.cpp index 27f46763..92f41613 100644 --- a/src/framework/graphics/font.cpp +++ b/src/framework/graphics/font.cpp @@ -68,7 +68,7 @@ void Font::load(const OTMLNodePtr& fontNode) void Font::drawText(const std::string& text, const Point& startPos) { - Size boxSize = g_graphics.getViewportSize() - startPos.toSize(); + Size boxSize = g_painter->getResolution() - startPos.toSize(); Rect screenCoords(startPos, boxSize); drawText(text, screenCoords, Fw::AlignTopLeft); } diff --git a/src/framework/graphics/framebuffer.cpp b/src/framework/graphics/framebuffer.cpp index 6efd0dea..88517f87 100644 --- a/src/framework/graphics/framebuffer.cpp +++ b/src/framework/graphics/framebuffer.cpp @@ -86,22 +86,13 @@ void FrameBuffer::resize(const Size& size) void FrameBuffer::bind() { g_painter->saveAndResetState(); - internalBind(); - - Matrix3 projectionMatrix = { 2.0f/m_texture->getWidth(), 0.0f, 0.0f, - 0.0f, -2.0f/m_texture->getHeight(), 0.0f, - -1.0f, 1.0f, 1.0f }; - g_painter->setProjectionMatrix(projectionMatrix); - - m_oldViewportSize = g_graphics.getViewportSize(); - g_graphics.setViewportSize(m_texture->getSize()); + g_painter->setResolution(m_texture->getSize()); } void FrameBuffer::release() { internalRelease(); - g_graphics.setViewportSize(m_oldViewportSize); g_painter->restoreSavedState(); } diff --git a/src/framework/graphics/graphics.cpp b/src/framework/graphics/graphics.cpp index faa9a1b8..eb3ce17c 100644 --- a/src/framework/graphics/graphics.cpp +++ b/src/framework/graphics/graphics.cpp @@ -195,51 +195,18 @@ bool Graphics::selectPainterEngine(PainterEngine painterEngine) void Graphics::resize(const Size& size) { - setViewportSize(size); - - // The projection matrix converts from Painter's coordinate system to GL's coordinate system - // * GL's viewport is 2x2, Painter's is width x height - // * GL has +y -> -y going from bottom -> top, Painter is the other way round - // * GL has [0,0] in the center, Painter has it in the top-left - // - // This results in the Projection matrix below. - // - // Projection Matrix - // Painter Coord ------------------------------------------------ GL Coord - // ------------- | 2.0 / width | 0.0 | 0.0 | --------------- - // | x y 1 | * | 0.0 | -2.0 / height | 0.0 | = | x' y' 1 | - // ------------- | -1.0 | 1.0 | 1.0 | --------------- - // ------------------------------------------------ - Matrix3 projectionMatrix = { 2.0f/size.width(), 0.0f, 0.0f, - 0.0f, -2.0f/size.height(), 0.0f, - -1.0f, 1.0f, 1.0f }; - + m_viewportSize = size; #ifdef PAINTER_OGL1 if(g_painterOGL1) - g_painterOGL1->setProjectionMatrix(projectionMatrix); + g_painterOGL1->setResolution(size); #endif #ifdef PAINTER_OGL2 if(g_painterOGL2) - g_painterOGL2->setProjectionMatrix(projectionMatrix); + g_painterOGL2->setResolution(size); #endif } -void Graphics::beginRender() -{ - //g_painter->clear(Color::black); -} - -void Graphics::endRender() -{ -} - -void Graphics::setViewportSize(const Size& size) -{ - glViewport(0, 0, size.width(), size.height()); - m_viewportSize = size; -} - bool Graphics::canUseDrawArrays() { #ifdef OPENGL_ES diff --git a/src/framework/graphics/graphics.h b/src/framework/graphics/graphics.h index ad728577..25a1ea76 100644 --- a/src/framework/graphics/graphics.h +++ b/src/framework/graphics/graphics.h @@ -46,8 +46,6 @@ public: PainterEngine getPainterEngine() { return m_selectedPainterEngine; } void resize(const Size& size); - void beginRender(); - void endRender(); void setViewportSize(const Size& size); diff --git a/src/framework/graphics/painter.cpp b/src/framework/graphics/painter.cpp index 2c3ddda9..d92fad10 100644 --- a/src/framework/graphics/painter.cpp +++ b/src/framework/graphics/painter.cpp @@ -54,6 +54,7 @@ void Painter::refreshState() updateGlClipRect(); updateGlTexture(); updateGlAlphaWriting(); + updateGlViewport(); } void Painter::saveState() @@ -68,6 +69,7 @@ void Painter::saveState() m_olderStates[m_oldStateIndex].shaderProgram = m_shaderProgram; m_olderStates[m_oldStateIndex].texture = m_texture; m_olderStates[m_oldStateIndex].alphaWriting = m_alphaWriting; + m_olderStates[m_oldStateIndex].resolution = m_resolution; m_oldStateIndex++; } @@ -88,6 +90,7 @@ void Painter::restoreSavedState() setClipRect(m_olderStates[m_oldStateIndex].clipRect); setShaderProgram(m_olderStates[m_oldStateIndex].shaderProgram); setTexture(m_olderStates[m_oldStateIndex].texture); + setResolution(m_olderStates[m_oldStateIndex].resolution); } void Painter::clear(const Color& color) @@ -150,6 +153,36 @@ void Painter::setAlphaWriting(bool enable) updateGlAlphaWriting(); } +void Painter::setResolution(const Size& resolution) +{ + if(m_resolution == resolution) + return; + + + // The projection matrix converts from Painter's coordinate system to GL's coordinate system + // * GL's viewport is 2x2, Painter's is width x height + // * GL has +y -> -y going from bottom -> top, Painter is the other way round + // * GL has [0,0] in the center, Painter has it in the top-left + // + // This results in the Projection matrix below. + // + // Projection Matrix + // Painter Coord ------------------------------------------------ GL Coord + // ------------- | 2.0 / width | 0.0 | 0.0 | --------------- + // | x y 1 | * | 0.0 | -2.0 / height | 0.0 | = | x' y' 1 | + // ------------- | -1.0 | 1.0 | 1.0 | --------------- + + Matrix3 projectionMatrix = { 2.0f/resolution.width(), 0.0f, 0.0f, + 0.0f, -2.0f/resolution.height(), 0.0f, + -1.0f, 1.0f, 1.0f }; + + m_resolution = resolution; + + setProjectionMatrix(projectionMatrix); + if(g_painter == this) + updateGlViewport(); +} + void Painter::updateGlTexture() { if(m_glTextureId != 0) @@ -184,7 +217,7 @@ void Painter::updateGlClipRect() { if(m_clipRect.isValid()) { glEnable(GL_SCISSOR_TEST); - glScissor(m_clipRect.left(), g_graphics.getViewportSize().height() - m_clipRect.bottom() - 1, m_clipRect.width(), m_clipRect.height()); + glScissor(m_clipRect.left(), m_resolution.height() - m_clipRect.bottom() - 1, m_clipRect.width(), m_clipRect.height()); } else { glDisable(GL_SCISSOR_TEST); } @@ -197,3 +230,8 @@ void Painter::updateGlAlphaWriting() else glColorMask(1,1,1,0); } + +void Painter::updateGlViewport() +{ + glViewport(0, 0, m_resolution.width(), m_resolution.height()); +} diff --git a/src/framework/graphics/painter.h b/src/framework/graphics/painter.h index 01eeecee..9ba17f90 100644 --- a/src/framework/graphics/painter.h +++ b/src/framework/graphics/painter.h @@ -53,6 +53,7 @@ public: Texture *texture; PainterShaderProgram *shaderProgram; bool alphaWriting; + Size resolution; }; Painter(); @@ -91,6 +92,7 @@ public: void setShaderProgram(const PainterShaderProgramPtr& shaderProgram) { setShaderProgram(shaderProgram.get()); } void setTexture(const TexturePtr& texture) { setTexture(texture.get()); } + void setResolution(const Size& resolution); Matrix3 getProjectionMatrix() { return m_projectionMatrix; } Matrix3 getTextureMatrix() { return m_textureMatrix; } @@ -100,6 +102,7 @@ public: Rect getClipRect() { return m_clipRect; } PainterShaderProgram *getShaderProgram() { return m_shaderProgram; } bool getAlphaWriting() { return m_alphaWriting; } + Size getResolution() { return m_resolution; }; void resetColor() { setColor(Color::white); } void resetOpacity() { setOpacity(1.0f); } @@ -109,11 +112,14 @@ public: void resetTexture() { setTexture(nullptr); } void resetAlphaWriting() { setAlphaWriting(false); } + virtual bool hasShaders() = 0; + protected: void updateGlTexture(); void updateGlCompositionMode(); void updateGlClipRect(); void updateGlAlphaWriting(); + void updateGlViewport(); CoordsBuffer m_coordsBuffer; @@ -126,6 +132,7 @@ protected: Texture *m_texture; PainterShaderProgram *m_shaderProgram; bool m_alphaWriting; + Size m_resolution; PainterState m_olderStates[10]; int m_oldStateIndex; diff --git a/src/framework/graphics/painterogl1.h b/src/framework/graphics/painterogl1.h index 1db679dc..cda15233 100644 --- a/src/framework/graphics/painterogl1.h +++ b/src/framework/graphics/painterogl1.h @@ -62,6 +62,8 @@ public: void setColor(const Color& color); void setOpacity(float opacity); + bool hasShaders() { return false; } + private: void updateGlColor(); void updateGlMatrixMode(); diff --git a/src/framework/graphics/painterogl2.cpp b/src/framework/graphics/painterogl2.cpp index bee48f95..48ab55ed 100644 --- a/src/framework/graphics/painterogl2.cpp +++ b/src/framework/graphics/painterogl2.cpp @@ -22,7 +22,9 @@ #include "painterogl2.h" #include "texture.h" -#include "paintershadermanager.h" +#include "graphics.h" +#include "painterogl2_shadersources.h" +#include PainterOGL2 *g_painterOGL2 = nullptr; @@ -31,12 +33,25 @@ PainterOGL2::PainterOGL2() m_drawProgram = nullptr; resetState(); - g_shaders.init(); + m_drawTexturedProgram = PainterShaderProgramPtr(new PainterShaderProgram); + assert(m_drawTexturedProgram); + m_drawTexturedProgram->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); + m_drawTexturedProgram->addShaderFromSourceCode(Shader::Fragment, glslMainFragmentShader + glslTextureSrcFragmentShader); + m_drawTexturedProgram->link(); + + m_drawSolidColorProgram = PainterShaderProgramPtr(new PainterShaderProgram); + assert(m_drawSolidColorProgram); + m_drawSolidColorProgram->addShaderFromSourceCode(Shader::Vertex, glslMainVertexShader + glslPositionOnlyVertexShader); + m_drawSolidColorProgram->addShaderFromSourceCode(Shader::Fragment, glslMainFragmentShader + glslSolidColorFragmentShader); + m_drawSolidColorProgram->link(); + + PainterShaderProgram::release(); } PainterOGL2::~PainterOGL2() { - g_shaders.terminate(); + m_drawTexturedProgram = nullptr; + m_drawSolidColorProgram = nullptr; } void PainterOGL2::bind() @@ -71,10 +86,13 @@ void PainterOGL2::drawCoords(CoordsBuffer& coordsBuffer, DrawMode drawMode) // update shader with the current painter state m_drawProgram->bind(); m_drawProgram->setProjectionMatrix(m_projectionMatrix); - if(textured) + if(textured) { m_drawProgram->setTextureMatrix(m_textureMatrix); + m_drawProgram->bindMultiTextures(); + } m_drawProgram->setOpacity(m_opacity); m_drawProgram->setColor(m_color); + m_drawProgram->setResolution(m_resolution); m_drawProgram->updateTime(); // update coords buffer hardware caches if enabled @@ -107,7 +125,7 @@ void PainterOGL2::drawTextureCoords(CoordsBuffer& coordsBuffer, const TexturePtr if(texture->isEmpty()) return; - setDrawProgram(m_shaderProgram ? m_shaderProgram : g_shaders.getDrawTexturedProgram().get()); + setDrawProgram(m_shaderProgram ? m_shaderProgram : m_drawTexturedProgram.get()); setTexture(texture); drawCoords(coordsBuffer); } @@ -117,7 +135,7 @@ void PainterOGL2::drawTexturedRect(const Rect& dest, const TexturePtr& texture, if(dest.isEmpty() || src.isEmpty() || texture->isEmpty()) return; - setDrawProgram(m_shaderProgram ? m_shaderProgram : g_shaders.getDrawTexturedProgram().get()); + setDrawProgram(m_shaderProgram ? m_shaderProgram : m_drawTexturedProgram.get()); setTexture(texture); m_coordsBuffer.clear(); @@ -130,7 +148,7 @@ void PainterOGL2::drawRepeatedTexturedRect(const Rect& dest, const TexturePtr& t if(dest.isEmpty() || src.isEmpty() || texture->isEmpty()) return; - setDrawProgram(m_shaderProgram ? m_shaderProgram : g_shaders.getDrawTexturedProgram().get()); + setDrawProgram(m_shaderProgram ? m_shaderProgram : m_drawTexturedProgram.get()); setTexture(texture); m_coordsBuffer.clear(); @@ -143,7 +161,7 @@ void PainterOGL2::drawFilledRect(const Rect& dest) if(dest.isEmpty()) return; - setDrawProgram(m_shaderProgram ? m_shaderProgram : g_shaders.getDrawSolidColorProgram().get()); + setDrawProgram(m_shaderProgram ? m_shaderProgram : m_drawSolidColorProgram.get()); m_coordsBuffer.clear(); m_coordsBuffer.addRect(dest); @@ -155,7 +173,7 @@ void PainterOGL2::drawFilledTriangle(const Point& a, const Point& b, const Point if(a == b || a == c || b == c) return; - setDrawProgram(m_shaderProgram ? m_shaderProgram : g_shaders.getDrawSolidColorProgram().get()); + setDrawProgram(m_shaderProgram ? m_shaderProgram : m_drawSolidColorProgram.get()); m_coordsBuffer.clear(); m_coordsBuffer.addTriangle(a, b, c); @@ -167,7 +185,7 @@ void PainterOGL2::drawBoundingRect(const Rect& dest, int innerLineWidth) if(dest.isEmpty() || innerLineWidth == 0) return; - setDrawProgram(m_shaderProgram ? m_shaderProgram : g_shaders.getDrawSolidColorProgram().get()); + setDrawProgram(m_shaderProgram ? m_shaderProgram : m_drawSolidColorProgram.get()); m_coordsBuffer.clear(); m_coordsBuffer.addBoudingRect(dest, innerLineWidth); diff --git a/src/framework/graphics/painterogl2.h b/src/framework/graphics/painterogl2.h index 3e3bfd68..8358d2bd 100644 --- a/src/framework/graphics/painterogl2.h +++ b/src/framework/graphics/painterogl2.h @@ -51,8 +51,12 @@ public: void setDrawProgram(PainterShaderProgram *drawProgram) { m_drawProgram = drawProgram; } + bool hasShaders() { return true; } + private: PainterShaderProgram *m_drawProgram; + PainterShaderProgramPtr m_drawTexturedProgram; + PainterShaderProgramPtr m_drawSolidColorProgram; }; extern PainterOGL2 *g_painterOGL2; diff --git a/src/framework/graphics/painterogl2_shadersources.h b/src/framework/graphics/painterogl2_shadersources.h index a4fd3fce..1607beca 100644 --- a/src/framework/graphics/painterogl2_shadersources.h +++ b/src/framework/graphics/painterogl2_shadersources.h @@ -27,42 +27,42 @@ static const std::string glslMainVertexShader = "\n\ }\n"; static const std::string glslMainWithTexCoordsVertexShader = "\n\ - attribute highp vec2 a_texCoord;\n\ - uniform highp mat3 textureMatrix;\n\ - varying highp vec2 texCoord;\n\ + attribute highp vec2 a_TexCoord;\n\ + uniform highp mat3 u_TextureMatrix;\n\ + varying highp vec2 v_TexCoord;\n\ highp vec4 calculatePosition();\n\ void main()\n\ {\n\ gl_Position = calculatePosition();\n\ - texCoord = (textureMatrix * vec3(a_texCoord,1)).xy;\n\ + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1)).xy;\n\ }\n"; static std::string glslPositionOnlyVertexShader = "\n\ - attribute highp vec2 a_vertex;\n\ - uniform highp mat3 projectionMatrix;\n\ + attribute highp vec2 a_Vertex;\n\ + uniform highp mat3 u_ProjectionMatrix;\n\ highp vec4 calculatePosition() {\n\ - return vec4(projectionMatrix * vec3(a_vertex.xy, 1), 1);\n\ + return vec4(u_ProjectionMatrix * vec3(a_Vertex.xy, 1), 1);\n\ }\n"; static const std::string glslMainFragmentShader = "\n\ - uniform lowp float opacity;\n\ + uniform lowp float u_Opacity;\n\ lowp vec4 calculatePixel();\n\ void main()\n\ {\n\ gl_FragColor = calculatePixel();\n\ - gl_FragColor.a *= opacity;\n\ + gl_FragColor.a *= u_Opacity;\n\ }\n"; static const std::string glslTextureSrcFragmentShader = "\n\ - varying mediump vec2 texCoord;\n\ - uniform lowp vec4 color;\n\ - uniform sampler2D tex0;\n\ + varying mediump vec2 v_TexCoord;\n\ + uniform lowp vec4 u_Color;\n\ + uniform sampler2D u_Tex0;\n\ lowp vec4 calculatePixel() {\n\ - return texture2D(tex0, texCoord) * color;\n\ + return texture2D(u_Tex0, v_TexCoord) * u_Color;\n\ }\n"; static const std::string glslSolidColorFragmentShader = "\n\ - uniform lowp vec4 color;\n\ + uniform lowp vec4 u_Color;\n\ lowp vec4 calculatePixel() {\n\ - return color;\n\ + return u_Color;\n\ }\n"; diff --git a/src/framework/graphics/paintershadermanager.cpp b/src/framework/graphics/paintershadermanager.cpp deleted file mode 100644 index 2dd7bf6d..00000000 --- a/src/framework/graphics/paintershadermanager.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2010-2012 OTClient - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "paintershadermanager.h" -#include "painterogl2.h" -#include "texture.h" -#include "painterogl2_shadersources.h" -#include "paintershaderprogram.h" -#include "shaderprogram.h" -#include "graphics.h" - -PainterShaderManager g_shaders; - -void PainterShaderManager::init() -{ - m_drawTexturedProgram = createShader(); - assert(m_drawTexturedProgram); - m_drawTexturedProgram->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); - m_drawTexturedProgram->addShaderFromSourceCode(Shader::Fragment, glslMainFragmentShader + glslTextureSrcFragmentShader); - m_drawTexturedProgram->link(); - - m_drawSolidColorProgram = createShader(); - assert(m_drawSolidColorProgram); - m_drawSolidColorProgram->addShaderFromSourceCode(Shader::Vertex, glslMainVertexShader + glslPositionOnlyVertexShader); - m_drawSolidColorProgram->addShaderFromSourceCode(Shader::Fragment, glslMainFragmentShader + glslSolidColorFragmentShader); - m_drawSolidColorProgram->link(); - - PainterShaderProgram::release(); -} - -void PainterShaderManager::terminate() -{ - m_drawTexturedProgram = nullptr; - m_drawSolidColorProgram = nullptr; -} - -PainterShaderProgramPtr PainterShaderManager::createShader() -{ - if(!g_graphics.canUseShaders()) - return nullptr; - return PainterShaderProgramPtr(new PainterShaderProgram); -} - -PainterShaderProgramPtr PainterShaderManager::createTexturedFragmentShader(const std::string& shaderFile) -{ - PainterShaderProgramPtr shader = createShader(); - if(!shader) - return nullptr; - - shader->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); - shader->addShaderFromSourceFile(Shader::Fragment, shaderFile); - - if(!shader->link()) - return nullptr; - - return shader; -} diff --git a/src/framework/graphics/paintershaderprogram.cpp b/src/framework/graphics/paintershaderprogram.cpp index 7d18dc03..faac9a66 100644 --- a/src/framework/graphics/paintershaderprogram.cpp +++ b/src/framework/graphics/paintershaderprogram.cpp @@ -24,7 +24,9 @@ #include "painter.h" #include "texture.h" #include "texturemanager.h" +#include "graphics.h" #include +#include PainterShaderProgram::PainterShaderProgram() { @@ -34,28 +36,35 @@ PainterShaderProgram::PainterShaderProgram() m_time = 0; } +void PainterShaderProgram::setupUniforms() +{ + bindUniformLocation(PROJECTION_MATRIX_UNIFORM, "u_ProjectionMatrix"); + bindUniformLocation(TEXTURE_MATRIX_UNIFORM, "u_TextureMatrix"); + bindUniformLocation(COLOR_UNIFORM, "u_Color"); + bindUniformLocation(OPACITY_UNIFORM, "u_Opacity"); + bindUniformLocation(TIME_UNIFORM, "u_Time"); + bindUniformLocation(TEX0_UNIFORM, "u_Tex0"); + bindUniformLocation(TEX1_UNIFORM, "u_Tex1"); + bindUniformLocation(RESOLUTION_UNIFORM, "u_Resolution"); + + setUniformValue(PROJECTION_MATRIX_UNIFORM, m_projectionMatrix); + setUniformValue(TEXTURE_MATRIX_UNIFORM, m_textureMatrix); + setUniformValue(COLOR_UNIFORM, m_color); + setUniformValue(OPACITY_UNIFORM, m_opacity); + setUniformValue(TIME_UNIFORM, m_time); + setUniformValue(TEX0_UNIFORM, 0); + setUniformValue(TEX1_UNIFORM, 1); + setUniformValue(RESOLUTION_UNIFORM, (float)m_resolution.width(), (float)m_resolution.height()); +} + bool PainterShaderProgram::link() { m_startTime = g_clock.seconds(); - bindAttributeLocation(VERTEX_ATTR, "a_vertex"); - bindAttributeLocation(TEXCOORD_ATTR, "a_texCoord"); + bindAttributeLocation(VERTEX_ATTR, "a_Vertex"); + bindAttributeLocation(TEXCOORD_ATTR, "a_TexCoord"); if(ShaderProgram::link()) { - bindUniformLocation(PROJECTION_MATRIX_UNIFORM, "projectionMatrix"); - bindUniformLocation(TEXTURE_MATRIX_UNIFORM, "textureMatrix"); - bindUniformLocation(COLOR_UNIFORM, "color"); - bindUniformLocation(OPACITY_UNIFORM, "opacity"); - bindUniformLocation(TIME_UNIFORM, "time"); - bindUniformLocation(TEX0_UNIFORM, "tex0"); - bindUniformLocation(TEX1_UNIFORM, "tex1"); - bind(); - setUniformValue(PROJECTION_MATRIX_UNIFORM, m_projectionMatrix); - setUniformValue(TEXTURE_MATRIX_UNIFORM, m_textureMatrix); - setUniformValue(COLOR_UNIFORM, m_color); - setUniformValue(OPACITY_UNIFORM, m_opacity); - setUniformValue(TIME_UNIFORM, m_time); - setUniformValue(TEX0_UNIFORM, 0); - setUniformValue(TEX1_UNIFORM, 1); + setupUniforms(); release(); return true; } @@ -102,6 +111,16 @@ void PainterShaderProgram::setOpacity(float opacity) m_opacity = opacity; } +void PainterShaderProgram::setResolution(const Size& resolution) +{ + if(m_resolution == resolution) + return; + + bind(); + setUniformValue(RESOLUTION_UNIFORM, (float)resolution.width(), (float)resolution.height()); + m_resolution = resolution; +} + void PainterShaderProgram::updateTime() { float time = g_clock.seconds() - m_startTime; @@ -112,3 +131,32 @@ void PainterShaderProgram::updateTime() setUniformValue(TIME_UNIFORM, time); m_time = time; } + +void PainterShaderProgram::addMultiTexture(const std::string& file) +{ + if(m_multiTextures.size() > 3) + g_logger.error("cannot add more multi textures to shader, the max is 3"); + + TexturePtr texture = g_textures.getTexture(file); + if(!texture) + return; + + texture->setSmooth(true); + texture->setRepeat(true); + + m_multiTextures.push_back(texture); +} + +void PainterShaderProgram::bindMultiTextures() +{ + if(m_multiTextures.size() == 0) + return; + + int i=1; + for(const TexturePtr& tex : m_multiTextures) { + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(GL_TEXTURE_2D, tex->getId()); + } + + glActiveTexture(GL_TEXTURE0); +} diff --git a/src/framework/graphics/paintershaderprogram.h b/src/framework/graphics/paintershaderprogram.h index 547753c7..49809a63 100644 --- a/src/framework/graphics/paintershaderprogram.h +++ b/src/framework/graphics/paintershaderprogram.h @@ -40,12 +40,13 @@ protected: TIME_UNIFORM = 4, TEX0_UNIFORM = 5, TEX1_UNIFORM = 6, - //TEX2_UNIFORM = 7, - //TEX3_UNIFORM = 8, + RESOLUTION_UNIFORM = 7, }; friend class PainterOGL2; + virtual void setupUniforms(); + public: PainterShaderProgram(); @@ -55,8 +56,12 @@ public: void setTextureMatrix(const Matrix3& textureMatrix); void setColor(const Color& color); void setOpacity(float opacity); + void setResolution(const Size& resolution); void updateTime(); + void addMultiTexture(const std::string& file); + void bindMultiTextures(); + private: float m_startTime; @@ -64,7 +69,9 @@ private: float m_opacity; Matrix3 m_projectionMatrix; Matrix3 m_textureMatrix; + Size m_resolution; float m_time; + std::vector m_multiTextures; }; #endif diff --git a/src/framework/graphics/shader.cpp b/src/framework/graphics/shader.cpp index ba6670e6..699f6547 100644 --- a/src/framework/graphics/shader.cpp +++ b/src/framework/graphics/shader.cpp @@ -72,8 +72,12 @@ bool Shader::compileSourceCode(const std::string& sourceCode) bool Shader::compileSourceFile(const std::string& sourceFile) { - std::string sourceCode = g_resources.loadFile(sourceFile); - return compileSourceCode(sourceCode); + try { + std::string sourceCode = g_resources.loadFile(sourceFile); + return compileSourceCode(sourceCode); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("unable to load shader source form file: %s", sourceFile)); + } } std::string Shader::log() diff --git a/src/framework/graphics/shaderprogram.h b/src/framework/graphics/shaderprogram.h index 31976820..e029c3d9 100644 --- a/src/framework/graphics/shaderprogram.h +++ b/src/framework/graphics/shaderprogram.h @@ -24,12 +24,14 @@ #define SHADERPROGRAM_H #include "shader.h" +#include -class ShaderProgram +class ShaderProgram : public LuaObject { enum { MAX_UNIFORM_LOCATIONS = 30 }; + public: ShaderProgram(); ~ShaderProgram(); diff --git a/src/framework/graphics/texture.cpp b/src/framework/graphics/texture.cpp index 1bc43945..9e8a460b 100644 --- a/src/framework/graphics/texture.cpp +++ b/src/framework/graphics/texture.cpp @@ -124,6 +124,16 @@ void Texture::setSmooth(bool smooth) setupFilters(); } +void Texture::setRepeat(bool repeat) +{ + if(m_repeat == repeat) + return; + + m_repeat = repeat; + bind(); + setupWrap(); +} + void Texture::setUpsideDown(bool upsideDown) { if(m_upsideDown == upsideDown) @@ -163,9 +173,11 @@ bool Texture::setupSize(const Size& size, bool forcePowerOfTwo) void Texture::setupWrap() { - GLint texParam = GL_REPEAT; - if(g_graphics.canUseClampToEdge()) - texParam = GL_CLAMP_TO_EDGE; // disable texture borders by default + GLint texParam; + if(!m_repeat && g_graphics.canUseClampToEdge()) + texParam = GL_CLAMP_TO_EDGE; + else + texParam = GL_REPEAT; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texParam); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texParam); diff --git a/src/framework/graphics/texture.h b/src/framework/graphics/texture.h index bc5bcc90..7939539e 100644 --- a/src/framework/graphics/texture.h +++ b/src/framework/graphics/texture.h @@ -38,6 +38,7 @@ public: bool buildHardwareMipmaps(); void setSmooth(bool smooth); + void setRepeat(bool repeat); void setUpsideDown(bool upsideDown); GLuint getId() { return m_id; } @@ -47,6 +48,7 @@ public: const Size& getGlSize() { return m_glSize; } const Matrix3& getTransformMatrix() { return m_transformMatrix; } bool isEmpty() { return m_id == 0; } + bool hasRepeat() { return m_repeat; } bool hasMipmaps() { return m_hasMipmaps; } protected: @@ -64,6 +66,7 @@ protected: Boolean m_hasMipmaps; Boolean m_smooth; Boolean m_upsideDown; + Boolean m_repeat; }; #endif diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 4e723c73..f3bf3fb8 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -473,6 +473,10 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("encryptRSA", &OutputMessage::encryptRSA); g_lua.bindClassMemberFunction("getMessageSize", &OutputMessage::getMessageSize); + g_lua.registerClass(); + g_lua.registerClass(); + g_lua.bindClassMemberFunction("addMultiTexture", &PainterShaderProgram::addMultiTexture); + // Application g_lua.registerStaticClass("g_app"); g_lua.bindClassStaticFunction("g_app", "exit", std::bind(&Application::exit, g_app)); diff --git a/src/otclient/CMakeLists.txt b/src/otclient/CMakeLists.txt index 8f128bbb..482571dc 100644 --- a/src/otclient/CMakeLists.txt +++ b/src/otclient/CMakeLists.txt @@ -44,8 +44,8 @@ SET(otclient_SOURCES ${otclient_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/effect.h ${CMAKE_CURRENT_LIST_DIR}/core/game.cpp ${CMAKE_CURRENT_LIST_DIR}/core/game.h - ${CMAKE_CURRENT_LIST_DIR}/core/gameshadermanager.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/gameshadermanager.h + ${CMAKE_CURRENT_LIST_DIR}/core/shadermanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/shadermanager.h ${CMAKE_CURRENT_LIST_DIR}/core/item.cpp ${CMAKE_CURRENT_LIST_DIR}/core/item.h ${CMAKE_CURRENT_LIST_DIR}/core/localplayer.cpp diff --git a/src/otclient/core/declarations.h b/src/otclient/core/declarations.h index 80a5bcbe..db5482da 100644 --- a/src/otclient/core/declarations.h +++ b/src/otclient/core/declarations.h @@ -43,6 +43,7 @@ class AnimatedText; class StaticText; class ThingType; class ThingsType; +class ItemShader; typedef std::shared_ptr MapViewPtr; typedef std::shared_ptr TilePtr; @@ -58,6 +59,7 @@ typedef std::shared_ptr EffectPtr; typedef std::shared_ptr MissilePtr; typedef std::shared_ptr AnimatedTextPtr; typedef std::shared_ptr StaticTextPtr; +typedef std::shared_ptr ItemShaderPtr; typedef std::vector ThingList; diff --git a/src/otclient/core/gameshadermanager.cpp b/src/otclient/core/gameshadermanager.cpp deleted file mode 100644 index f5ff369b..00000000 --- a/src/otclient/core/gameshadermanager.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 OTClient - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "gameshadermanager.h" - diff --git a/src/otclient/core/gameshadermanager.h b/src/otclient/core/gameshadermanager.h deleted file mode 100644 index d1e4acdb..00000000 --- a/src/otclient/core/gameshadermanager.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2010-2012 OTClient - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef GAMESHADERMANAGER_H -#define GAMESHADERMANAGER_H - -class GameShaderManager -{ -}; - -#endif diff --git a/src/otclient/core/item.cpp b/src/otclient/core/item.cpp index 49e7fdf4..419c07fc 100644 --- a/src/otclient/core/item.cpp +++ b/src/otclient/core/item.cpp @@ -25,16 +25,16 @@ #include "spritemanager.h" #include "thing.h" #include "tile.h" +#include "shadermanager.h" #include #include #include -#include -#include Item::Item() : Thing() { m_id = 0; m_countOrSubType = 1; + m_shaderProgram = g_shaders.getDefaultItemShader(); } ItemPtr Item::create(int id) @@ -48,10 +48,6 @@ ItemPtr Item::create(int id) return item; } -/* -PainterShaderProgramPtr itemProgram; -int ITEM_ID_UNIFORM = 10; -*/ void Item::draw(const Point& dest, float scaleFactor, bool animate) { if(m_id == 0) @@ -166,25 +162,18 @@ void Item::draw(const Point& dest, float scaleFactor, bool animate) zPattern = m_position.z % getNumPatternsZ(); } - // setup item drawing shader - /* - if(!itemProgram) { - itemProgram = PainterShaderProgramPtr(new PainterShaderProgram); - itemProgram->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); - itemProgram->addShaderFromSourceFile(Shader::Fragment, "/game_shaders/item.frag"); - itemProgram->link(); - itemProgram->bindUniformLocation(ITEM_ID_UNIFORM, "itemId"); + bool useShader = g_painter->hasShaders() && m_shaderProgram; + if(useShader) { + m_shaderProgram->bind(); + m_shaderProgram->setUniformValue(ShaderManager::ITEM_ID_UNIFORM, (int)m_id); + + g_painter->setShaderProgram(m_shaderProgram); } - g_painter->setShaderProgram(itemProgram); - //itemProgram->bind(); - //itemProgram->setUniformValue(ITEM_ID_UNIFORM, (int)m_id); - */ - // now we can draw the item m_type->draw(dest, scaleFactor, 0, xPattern, yPattern, zPattern, animationPhase); - // release draw shader - //g_painter->resetShaderProgram(); + if(useShader) + g_painter->resetShaderProgram(); } void Item::setId(uint32 id) diff --git a/src/otclient/core/item.h b/src/otclient/core/item.h index be2998c5..62054a2d 100644 --- a/src/otclient/core/item.h +++ b/src/otclient/core/item.h @@ -51,6 +51,7 @@ public: private: uint16 m_id; uint8 m_countOrSubType; + PainterShaderProgramPtr m_shaderProgram; }; #endif diff --git a/src/otclient/core/mapview.cpp b/src/otclient/core/mapview.cpp index a7795b87..ec8088b7 100644 --- a/src/otclient/core/mapview.cpp +++ b/src/otclient/core/mapview.cpp @@ -24,13 +24,13 @@ #include #include -#include #include "creature.h" #include "map.h" #include "tile.h" #include "statictext.h" #include "animatedtext.h" #include "missile.h" +#include "shadermanager.h" #include MapView::MapView() @@ -44,7 +44,7 @@ MapView::MapView() m_framebuffer = FrameBufferPtr(new FrameBuffer()); setVisibleDimension(Size(15, 11)); - m_shaderProgram = g_shaders.createTexturedFragmentShader("/game_shaders/map.frag"); + m_shader = g_shaders.getDefaultMapShader(); } void MapView::draw(const Rect& rect) @@ -109,12 +109,11 @@ void MapView::draw(const Rect& rect) m_framebuffer->release(); // generating mipmaps each frame can be slow in older cards - //m_framebuffer->getTexture()->generateHardwareMipmaps(); + //m_framebuffer->getTexture()->buildHardwareMipmaps(); m_mustDrawVisibleTilesCache = false; } - //g_painter->setShaderProgram(m_shaderProgram); Point drawOffset = ((m_drawDimension - m_visibleDimension - Size(1,1)).toPoint()/2) * m_tileSize; if(m_followingCreature) @@ -129,6 +128,7 @@ void MapView::draw(const Rect& rect) g_painter->setColor(Color::white); glDisable(GL_BLEND); + g_painter->setShaderProgram(m_shader); #if 0 // debug source area g_painter->saveAndResetState(); @@ -141,9 +141,9 @@ void MapView::draw(const Rect& rect) #else m_framebuffer->draw(rect, srcRect); #endif + g_painter->resetShaderProgram(); glEnable(GL_BLEND); - //g_painter->resetShaderProgram(); // this could happen if the player position is not known yet if(!cameraPosition.isValid()) diff --git a/src/otclient/core/mapview.h b/src/otclient/core/mapview.h index 0c9d7153..a6e9be1a 100644 --- a/src/otclient/core/mapview.h +++ b/src/otclient/core/mapview.h @@ -103,6 +103,9 @@ public: void setAnimated(bool animated) { m_animated = animated; requestVisibleTilesCacheUpdate(); } bool isAnimating() { return m_animated; } + void setShader(const PainterShaderProgramPtr& shader) { m_shader = shader; } + PainterShaderProgramPtr getShader() { return m_shader; } + // get tile TilePtr getTile(const Point& mousePos, const Rect& mapRect); @@ -139,7 +142,7 @@ private: EventPtr m_updateTilesCacheEvent; CreaturePtr m_followingCreature; FrameBufferPtr m_framebuffer; - PainterShaderProgramPtr m_shaderProgram; + PainterShaderProgramPtr m_shader; ViewMode m_viewMode; Otc::DrawFlags m_drawFlags; }; diff --git a/src/otclient/core/shadermanager.cpp b/src/otclient/core/shadermanager.cpp new file mode 100644 index 00000000..88892a45 --- /dev/null +++ b/src/otclient/core/shadermanager.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shadermanager.h" +#include +#include +#include + +ShaderManager g_shaders; + +void ShaderManager::init() +{ + if(!g_graphics.canUseShaders()) + return; + + m_defaultItemShader = createFragmentShaderFromCode("Item", glslMainFragmentShader + glslTextureSrcFragmentShader); + setupItemShader(m_defaultItemShader); + + m_defaultMapShader = createFragmentShaderFromCode("Map", glslMainFragmentShader + glslTextureSrcFragmentShader); + + PainterShaderProgram::release(); +} + +void ShaderManager::terminate() +{ + m_defaultItemShader = nullptr; + m_defaultMapShader = nullptr; +} + +PainterShaderProgramPtr ShaderManager::createShader(const std::string& name) +{ + if(!g_graphics.canUseShaders()) { + g_logger.error(stdext::format("unable to create shader, shaders are not supported")); + return nullptr; + } + + PainterShaderProgramPtr shader(new PainterShaderProgram); + m_shaders[name] = shader; + return shader; +} + +PainterShaderProgramPtr ShaderManager::createFragmentShader(const std::string& name, const std::string& file) +{ + PainterShaderProgramPtr shader = createShader(name); + if(!shader) + return nullptr; + + shader->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); + if(!shader->addShaderFromSourceFile(Shader::Fragment, file)) { + g_logger.error(stdext::format("unable to load fragment shader '%s' from source file '%s'", name, file)); + return nullptr; + } + + if(!shader->link()) { + g_logger.error(stdext::format("unable to link shader '%s' from file '%s'", name, file)); + return nullptr; + } + + m_shaders[name] = shader; + return shader; +} + +PainterShaderProgramPtr ShaderManager::createFragmentShaderFromCode(const std::string& name, const std::string& code) +{ + PainterShaderProgramPtr shader = createShader(name); + if(!shader) + return nullptr; + + shader->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); + if(!shader->addShaderFromSourceCode(Shader::Fragment, code)) { + g_logger.error(stdext::format("unable to load fragment shader '%s'", name)); + return nullptr; + } + + if(!shader->link()) { + g_logger.error(stdext::format("unable to link shader '%s'", name)); + return nullptr; + } + + m_shaders[name] = shader; + return shader; +} + +PainterShaderProgramPtr ShaderManager::createItemShader(const std::string& name, const std::string& file) +{ + PainterShaderProgramPtr shader = createFragmentShader(name, file); + if(shader) + setupItemShader(shader); + return shader; +} + +void ShaderManager::setupItemShader(const PainterShaderProgramPtr& shader) +{ + if(!shader) + return; + shader->bindUniformLocation(ITEM_ID_UNIFORM, "u_ItemId"); +} + +PainterShaderProgramPtr ShaderManager::getShader(const std::string& name) +{ + auto it = m_shaders.find(name); + if(it != m_shaders.end()) + return it->second; + return nullptr; +} diff --git a/src/otclient/core/shadermanager.h b/src/otclient/core/shadermanager.h new file mode 100644 index 00000000..f2a9e908 --- /dev/null +++ b/src/otclient/core/shadermanager.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GAMESHADERS_H +#define GAMESHADERS_H + +#include "declarations.h" +#include + +class ShaderManager +{ +public: + enum { + ITEM_ID_UNIFORM = 10 + }; + + void init(); + void terminate(); + + PainterShaderProgramPtr createShader(const std::string& name); + PainterShaderProgramPtr createFragmentShader(const std::string& name, const std::string& file); + PainterShaderProgramPtr createFragmentShaderFromCode(const std::string& name, const std::string& code); + + PainterShaderProgramPtr createItemShader(const std::string& name, const std::string& file); + PainterShaderProgramPtr createMapShader(const std::string& name, const std::string& file) { return createFragmentShader(name, file); } + + PainterShaderProgramPtr getDefaultItemShader() { return m_defaultItemShader; } + PainterShaderProgramPtr getDefaultMapShader() { return m_defaultMapShader; } + + PainterShaderProgramPtr getShader(const std::string& name); + +private: + void setupItemShader(const PainterShaderProgramPtr& shader); + + PainterShaderProgramPtr m_defaultItemShader; + PainterShaderProgramPtr m_defaultMapShader; + std::unordered_map m_shaders; +}; + + +extern ShaderManager g_shaders; + +#endif diff --git a/src/otclient/luafunctions.cpp b/src/otclient/luafunctions.cpp index 94bf3188..7a37ef2b 100644 --- a/src/otclient/luafunctions.cpp +++ b/src/otclient/luafunctions.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -171,6 +172,16 @@ void OTClient::registerLuaFunctions() g_lua.bindClassStaticFunction("g_game", "getClientVersion", std::bind(&Game::getClientVersion, &g_game)); g_lua.bindClassStaticFunction("g_game", "getFeature", std::bind(&Game::getFeature, &g_game, std::placeholders::_1)); + g_lua.registerStaticClass("g_shaders"); + g_lua.bindClassStaticFunction("g_shaders", "createShader", std::bind(&ShaderManager::createShader, &g_shaders, std::placeholders::_1)); + g_lua.bindClassStaticFunction("g_shaders", "createFragmentShader", std::bind(&ShaderManager::createFragmentShader, &g_shaders, std::placeholders::_1, std::placeholders::_2)); + g_lua.bindClassStaticFunction("g_shaders", "createFragmentShaderFromCode", std::bind(&ShaderManager::createFragmentShaderFromCode, &g_shaders, std::placeholders::_1, std::placeholders::_2)); + g_lua.bindClassStaticFunction("g_shaders", "createItemShader", std::bind(&ShaderManager::createItemShader, &g_shaders, std::placeholders::_1, std::placeholders::_2)); + g_lua.bindClassStaticFunction("g_shaders", "createMapShader", std::bind(&ShaderManager::createMapShader, &g_shaders, std::placeholders::_1, std::placeholders::_2)); + g_lua.bindClassStaticFunction("g_shaders", "getDefaultItemShader", std::bind(&ShaderManager::getDefaultItemShader, &g_shaders)); + g_lua.bindClassStaticFunction("g_shaders", "getDefaultMapShader", std::bind(&ShaderManager::getDefaultMapShader, &g_shaders)); + g_lua.bindClassStaticFunction("g_shaders", "getShader", std::bind(&ShaderManager::getShader, &g_shaders, std::placeholders::_1)); + g_lua.bindGlobalFunction("getOufitColor", Outfit::getColor); g_lua.registerClass(); @@ -375,6 +386,7 @@ void OTClient::registerLuaFunctions() g_lua.bindClassMemberFunction("setDrawMinimapColors", &UIMap::setDrawMinimapColors); g_lua.bindClassMemberFunction("setAnimated", &UIMap::setAnimated); g_lua.bindClassMemberFunction("setKeepAspectRatio", &UIMap::setKeepAspectRatio); + g_lua.bindClassMemberFunction("setMapShader", &UIMap::setMapShader); g_lua.bindClassMemberFunction("isMultifloor", &UIMap::isMultifloor); g_lua.bindClassMemberFunction("isAutoViewModeEnabled", &UIMap::isAutoViewModeEnabled); g_lua.bindClassMemberFunction("isDrawingTexts", &UIMap::isDrawingTexts); @@ -390,6 +402,7 @@ void OTClient::registerLuaFunctions() g_lua.bindClassMemberFunction("getMaxZoomIn", &UIMap::getMaxZoomIn); g_lua.bindClassMemberFunction("getMaxZoomOut", &UIMap::getMaxZoomOut); g_lua.bindClassMemberFunction("getZoom", &UIMap::getZoom); + g_lua.bindClassMemberFunction("getMapShader", &UIMap::getMapShader); g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return UIProgressRectPtr(new UIProgressRect); } ); diff --git a/src/otclient/otclient.cpp b/src/otclient/otclient.cpp index 4c73d8a3..f2153c0f 100644 --- a/src/otclient/otclient.cpp +++ b/src/otclient/otclient.cpp @@ -26,6 +26,7 @@ #include #include "core/game.h" #include "core/map.h" +#include "core/shadermanager.h" OTClient::OTClient() : Application(Otc::AppCompactName) { @@ -87,6 +88,8 @@ void OTClient::init(const std::vector& args) g_logger.setLogFile(stdext::format("%s.txt", Otc::AppCompactName)); Application::init(args); + g_shaders.init(); + g_modules.discoverModules(); // core modules 0-99 @@ -110,3 +113,9 @@ void OTClient::init(const std::vector& args) } } } + +void OTClient::terminate() +{ + g_shaders.terminate(); + Application::terminate(); +} diff --git a/src/otclient/otclient.h b/src/otclient/otclient.h index 1401ee17..2e6ed4e1 100644 --- a/src/otclient/otclient.h +++ b/src/otclient/otclient.h @@ -31,6 +31,7 @@ class OTClient : public Application public: OTClient(); void init(const std::vector& args); + void terminate(); void registerLuaFunctions(); }; diff --git a/src/otclient/ui/uimap.h b/src/otclient/ui/uimap.h index 3c021b17..1dcb263e 100644 --- a/src/otclient/ui/uimap.h +++ b/src/otclient/ui/uimap.h @@ -54,6 +54,7 @@ public: void setDrawMinimapColors(bool enable) { m_mapView->setDrawMinimapColors(enable); } void setAnimated(bool enable) { m_mapView->setAnimated(enable); } void setKeepAspectRatio(bool enable); + void setMapShader(const PainterShaderProgramPtr& shader) { m_mapView->setShader(shader); } bool isMultifloor() { return m_mapView->isMultifloor(); } bool isAutoViewModeEnabled() { return m_mapView->isAutoViewModeEnabled(); } @@ -71,6 +72,7 @@ public: int getMaxZoomIn() { return m_maxZoomIn; } int getMaxZoomOut() { return m_maxZoomOut; } int getZoom() { return m_zoom; } + PainterShaderProgramPtr getMapShader() { return m_mapView->getShader(); } protected: virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode);