1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "framebuffer.h"
24#include "graphics.h"
25#include "texture.h"
26
27#include <framework/platform/platformwindow.h>
28#include <framework/core/application.h>
29#include <framework/core/eventdispatcher.h>
30#include <framework/core/asyncdispatcher.h>
31#include <framework/graphics/image.h>
32
33uint FrameBuffer::boundFbo = 0;
34
35FrameBuffer::FrameBuffer(bool withDepth)
36{
37#ifdef WITH_DEPTH_BUFFER
38 m_depth = withDepth;
39#else
40 if(withDepth)
41 g_logger.fatal("Depth buffer is not supported. Compile OTCv8 with WITH_DEPTH_BUFFER definition.");
42#endif
43 internalCreate();
44}
45
46void FrameBuffer::internalCreate()
47{
48 m_prevBoundFbo = 0;
49 m_fbo = 0;
50 glGenFramebuffers(1, &m_fbo);
51 if (!m_fbo)
52 g_logger.fatal("Unable to create framebuffer object");
53#ifdef WITH_DEPTH_BUFFER
54 if (m_depth) {
55 glGenRenderbuffers(1, &m_depthRbo);
56 if (!m_depthRbo)
57 g_logger.fatal("Unable to create renderbuffer object");
58 }
59#endif
60 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
61}
62
63FrameBuffer::~FrameBuffer()
64{
65 VALIDATE(!g_app.isTerminated());
66 if (g_graphics.ok() && m_fbo != 0) {
67 if (m_fbo != 0)
68 glDeleteFramebuffers(1, &m_fbo);
69#ifdef WITH_DEPTH_BUFFER
70 if (m_depthRbo != 0)
71 glDeleteRenderbuffers(1, &m_depthRbo);
72#endif
73 }
74 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
75}
76
77void FrameBuffer::resize(const Size& size)
78{
79 if (!size.isValid())
80 g_logger.fatal(stdext::format("Invalid framebuffer size: %ix%i", size.width(), size.height()));
81
82 if (m_texture && m_texture->getSize() == size)
83 return;
84
85 m_texture = TexturePtr(new Texture(size, false, m_smooth, true));
86 m_texture->update();
87
88#ifdef WITH_DEPTH_BUFFER
89 if (m_depth) {
90 glBindRenderbuffer(GL_RENDERBUFFER, m_depthRbo);
91 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width(), size.height());
92 }
93#endif
94
95 internalBind();
96 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture->getId(), 0);
97
98#ifdef WITH_DEPTH_BUFFER
99 if (m_depth) {
100 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthRbo);
101 }
102#endif
103
104 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
105 if (status != GL_FRAMEBUFFER_COMPLETE)
106 g_logger.fatal(stdext::format("Unable to setup framebuffer object - %i - %ix%i", status, size.width(), size.height()));
107 internalRelease();
108 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
109}
110
111void FrameBuffer::bind(const FrameBufferPtr& depthFramebuffer)
112{
113 g_painter->saveAndResetState();
114 internalBind();
115 g_painter->setResolution(m_texture->getSize());
116#ifdef WITH_DEPTH_BUFFER
117 if (!m_depth && depthFramebuffer && depthFramebuffer->hasDepth()) {
118 m_depthRbo = depthFramebuffer->getDepthRenderBuffer();
119 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthRbo);
120 }
121#endif
122 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
123}
124
125void FrameBuffer::release()
126{
127#ifdef WITH_DEPTH_BUFFER
128 if (!m_depth && m_depthRbo) {
129 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
130 m_depthRbo = 0;
131 }
132#endif
133 internalRelease();
134 g_painter->restoreSavedState();
135 g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__);
136}
137
138void FrameBuffer::draw()
139{
140 Rect rect(0, 0, getSize());
141 g_painter->drawTexturedRect(rect, m_texture, rect);
142}
143
144void FrameBuffer::draw(const Rect& dest, const Rect& src)
145{
146 g_painter->drawTexturedRect(dest, m_texture, src);
147}
148
149void FrameBuffer::draw(const Rect& dest)
150{
151 g_painter->drawTexturedRect(dest, m_texture, Rect(0, 0, getSize()));
152}
153
154std::vector<uint32_t> FrameBuffer::readPixels()
155{
156 internalBind();
157 Size size = getSize();
158 int width = size.width();
159 int height = size.height();
160 std::vector<uint32_t> ret(width * height * sizeof(GLubyte), 0);
161 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLubyte*)(ret.data()));
162 internalRelease();
163 int halfWidth = width / 2;
164 for (int y = 0; y < height; ++y) {
165 for (int x = 0; x < halfWidth; ++x) {
166 std::swap(ret[y * width + x], ret[y * width + width - x - 1]);
167 }
168 }
169 for (int y = 0; y < height; ++y) {
170 for (int x = 0; x < halfWidth; ++x) {
171 std::swap(ret[y * width + x], ret[(height - y - 1) * width + width - x - 1]);
172 }
173 }
174 return ret;
175}
176
177void FrameBuffer::internalBind()
178{
179 VALIDATE(boundFbo != m_fbo);
180 glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
181 m_prevBoundFbo = boundFbo;
182 boundFbo = m_fbo;
183}
184
185void FrameBuffer::internalRelease()
186{
187 VALIDATE(boundFbo == m_fbo);
188 glBindFramebuffer(GL_FRAMEBUFFER, m_prevBoundFbo);
189 boundFbo = m_prevBoundFbo;
190}
191
192Size FrameBuffer::getSize()
193{
194 return m_texture->getSize();
195}
196
197void FrameBuffer::setSmooth(bool value)
198{
199 if (!m_texture || m_smooth == value) return;
200
201 m_smooth = value;
202 m_texture->setSmooth(value);
203}
204
205void FrameBuffer::doScreenshot(std::string fileName)
206{
207 if (g_mainThreadId != std::this_thread::get_id()) {
208 g_graphicsDispatcher.addEvent(std::bind(&FrameBuffer::doScreenshot, this, fileName));
209 return;
210 }
211
212 if (fileName.empty()) {
213 fileName = "screenshot_map.png";
214 }
215
216 internalBind();
217 Size size = getSize();
218 int width = size.width();
219 int height = size.height();
220 auto pixels = std::make_shared<std::vector<uint8_t>>(width * height * 4 * sizeof(GLubyte), 0);
221 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLubyte*)(pixels->data()));
222 internalRelease();
223
224 g_asyncDispatcher.dispatch([size, pixels, fileName] {
225 for (int line = 0, h = size.height(), w = size.width(); line != h / 2; ++line) {
226 std::swap_ranges(
227 pixels->begin() + 4 * w * line,
228 pixels->begin() + 4 * w * (line + 1),
229 pixels->begin() + 4 * w * (h - line - 1));
230 }
231 try {
232 Image image(size, 4, pixels->data());
233 image.savePNG(fileName);
234 }
235 catch (stdext::exception& e) {
236 g_logger.error(std::string("Can't do map screenshot: ") + e.what());
237 }
238 });
239}