1 /*
2 * Copyright 2010 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "graphic/texture.h"
20
21 #include <cassert>
22
23 #include <SDL_surface.h>
24
25 #include "base/log.h"
26 #include "base/macros.h"
27 #include "base/wexception.h"
28 #include "graphic/gl/blit_program.h"
29 #include "graphic/gl/draw_line_program.h"
30 #include "graphic/gl/fill_rect_program.h"
31 #include "graphic/gl/utils.h"
32 #include "graphic/sdl_utils.h"
33 #include "graphic/surface.h"
34
35 namespace {
36
37 namespace {
38
39 /**
40 * \return the standard 32-bit RGBA format that we use for our textures.
41 */
rgba_format()42 const SDL_PixelFormat& rgba_format() {
43 static SDL_PixelFormat format;
44 static bool init = false;
45 if (init) {
46 return format;
47 }
48
49 init = true;
50 memset(&format, 0, sizeof(format));
51 format.BitsPerPixel = 32;
52 format.BytesPerPixel = 4;
53 format.Rmask = 0x000000ff;
54 format.Gmask = 0x0000ff00;
55 format.Bmask = 0x00ff0000;
56 format.Amask = 0xff000000;
57 format.Rshift = 0;
58 format.Gshift = 8;
59 format.Bshift = 16;
60 format.Ashift = 24;
61 return format;
62 }
63
64 } // namespace
65
66 class GlFramebuffer {
67 public:
instance()68 static GlFramebuffer& instance() {
69 static GlFramebuffer gl_framebuffer;
70 return gl_framebuffer;
71 }
72
~GlFramebuffer()73 ~GlFramebuffer() {
74 glDeleteFramebuffers(1, &gl_framebuffer_id_);
75 }
76
id() const77 GLuint id() const {
78 return gl_framebuffer_id_;
79 }
80
81 private:
GlFramebuffer()82 GlFramebuffer() {
83 // Generate the framebuffer for Offscreen rendering.
84 glGenFramebuffers(1, &gl_framebuffer_id_);
85 }
86
87 GLuint gl_framebuffer_id_;
88
89 DISALLOW_COPY_AND_ASSIGN(GlFramebuffer);
90 };
91
is_bgr_surface(const SDL_PixelFormat & fmt)92 bool is_bgr_surface(const SDL_PixelFormat& fmt) {
93 return (fmt.Bmask == 0x000000ff && fmt.Gmask == 0x0000ff00 && fmt.Rmask == 0x00ff0000);
94 }
95
96 } // namespace
97
Texture(int w,int h)98 Texture::Texture(int w, int h) : owns_texture_(false) {
99 init(w, h);
100
101 if (blit_data_.texture_id == 0) {
102 return;
103 }
104
105 glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(GL_RGBA), width(), height(), 0, GL_RGBA,
106 GL_UNSIGNED_BYTE, nullptr);
107 }
108
Texture(SDL_Surface * surface,bool intensity)109 Texture::Texture(SDL_Surface* surface, bool intensity) : owns_texture_(false) {
110 init(surface->w, surface->h);
111
112 // Convert image data. BGR Surface support is an extension for
113 // OpenGL ES 2, which we rather not rely on. So we convert our
114 // surfaces in software.
115 // TODO(sirver): SDL_TTF returns all data in BGR format. If we
116 // use freetype directly we might be able to avoid that.
117 uint8_t bpp = surface->format->BytesPerPixel;
118
119 if (surface->format->palette || width() != surface->w || height() != surface->h ||
120 (bpp != 3 && bpp != 4) || is_bgr_surface(*surface->format)) {
121 SDL_Surface* converted = empty_sdl_surface(width(), height());
122 assert(converted);
123 SDL_SetSurfaceAlphaMod(converted, SDL_ALPHA_OPAQUE);
124 SDL_SetSurfaceBlendMode(converted, SDL_BLENDMODE_NONE);
125 SDL_SetSurfaceAlphaMod(surface, SDL_ALPHA_OPAQUE);
126 SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
127 SDL_BlitSurface(surface, nullptr, converted, nullptr);
128 SDL_FreeSurface(surface);
129 surface = converted;
130 bpp = surface->format->BytesPerPixel;
131 }
132
133 const GLenum pixels_format = bpp == 4 ? GL_RGBA : GL_RGB;
134
135 SDL_LockSurface(surface);
136
137 Gl::swap_rows(width(), height(), surface->pitch, bpp, static_cast<uint8_t*>(surface->pixels));
138
139 glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(intensity ? GL_INTENSITY : GL_RGBA), width(),
140 height(), 0, pixels_format, GL_UNSIGNED_BYTE, surface->pixels);
141
142 SDL_UnlockSurface(surface);
143 SDL_FreeSurface(surface);
144 }
145
Texture(const GLuint texture,const Recti & subrect,int parent_w,int parent_h)146 Texture::Texture(const GLuint texture, const Recti& subrect, int parent_w, int parent_h)
147 : owns_texture_(false) {
148 if (parent_w == 0 || parent_h == 0) {
149 throw wexception("Created a sub Texture with zero height and width parent.");
150 }
151
152 blit_data_ = BlitData{
153 texture, parent_w, parent_h, subrect.cast<float>(),
154 };
155 }
156
~Texture()157 Texture::~Texture() {
158 if (owns_texture_) {
159 Gl::State::instance().delete_texture(blit_data_.texture_id);
160 }
161 }
162
width() const163 int Texture::width() const {
164 return blit_data_.rect.w;
165 }
166
height() const167 int Texture::height() const {
168 return blit_data_.rect.h;
169 }
170
init(uint16_t w,uint16_t h)171 void Texture::init(uint16_t w, uint16_t h) {
172 blit_data_ = {
173 0, // initialized below
174 w, h, Rectf(0.f, 0.f, w, h),
175 };
176 if (w * h == 0) {
177 return;
178 }
179
180 owns_texture_ = true;
181 glGenTextures(1, &blit_data_.texture_id);
182 Gl::State::instance().bind(GL_TEXTURE0, blit_data_.texture_id);
183
184 // set texture filter to use linear filtering. This looks nicer for resized
185 // texture. Most textures and images are not resized so the filtering
186 // makes no difference.
187 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast<GLint>(GL_LINEAR));
188 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(GL_LINEAR));
189 }
190
lock()191 void Texture::lock() {
192 if (blit_data_.texture_id == 0) {
193 return;
194 }
195
196 if (pixels_) {
197 throw wexception("Called lock() on locked surface.");
198 }
199 if (!owns_texture_) {
200 throw wexception("A surface that does not own its pixels can not be locked..");
201 }
202
203 pixels_.reset(new uint8_t[width() * height() * 4]);
204
205 Gl::State::instance().bind(GL_TEXTURE0, blit_data_.texture_id);
206 glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels_.get());
207 }
208
unlock(UnlockMode mode)209 void Texture::unlock(UnlockMode mode) {
210 if (width() <= 0 || height() <= 0) {
211 return;
212 }
213 assert(pixels_);
214
215 if (mode == Unlock_Update) {
216 Gl::State::instance().bind(GL_TEXTURE0, blit_data_.texture_id);
217 glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(GL_RGBA), width(), height(), 0, GL_RGBA,
218 GL_UNSIGNED_BYTE, pixels_.get());
219 }
220
221 pixels_.reset(nullptr);
222 }
223
get_pixel(uint16_t x,uint16_t y)224 RGBAColor Texture::get_pixel(uint16_t x, uint16_t y) {
225 assert(pixels_);
226 assert(x < width());
227 assert(y < height());
228
229 RGBAColor color;
230
231 SDL_GetRGBA(*reinterpret_cast<uint32_t*>(&pixels_[(height() - y - 1) * 4 * width() + 4 * x]),
232 &rgba_format(), &color.r, &color.g, &color.b, &color.a);
233 return color;
234 }
235
set_pixel(uint16_t x,uint16_t y,const RGBAColor & color)236 void Texture::set_pixel(uint16_t x, uint16_t y, const RGBAColor& color) {
237 assert(pixels_);
238 assert(x < width());
239 assert(y < height());
240
241 uint8_t* data = &pixels_[(height() - y - 1) * 4 * width() + 4 * x];
242 uint32_t packed_color = SDL_MapRGBA(&rgba_format(), color.r, color.g, color.b, color.a);
243 *(reinterpret_cast<uint32_t*>(data)) = packed_color;
244 }
245
setup_gl()246 void Texture::setup_gl() {
247 assert(blit_data_.texture_id != 0);
248 Gl::State::instance().bind_framebuffer(GlFramebuffer::instance().id(), blit_data_.texture_id);
249 glViewport(0, 0, width(), height());
250 }
251
do_blit(const Rectf & dst_rect,const BlitData & texture,float opacity,BlendMode blend_mode)252 void Texture::do_blit(const Rectf& dst_rect,
253 const BlitData& texture,
254 float opacity,
255 BlendMode blend_mode) {
256 if (blit_data_.texture_id == 0) {
257 return;
258 }
259 setup_gl();
260 BlitProgram::instance().draw(dst_rect, 0.f, texture, BlitData{0, 0, 0, Rectf()},
261 RGBAColor(0, 0, 0, 255 * opacity), blend_mode);
262 }
263
do_blit_blended(const Rectf & dst_rect,const BlitData & texture,const BlitData & mask,const RGBColor & blend)264 void Texture::do_blit_blended(const Rectf& dst_rect,
265 const BlitData& texture,
266 const BlitData& mask,
267 const RGBColor& blend) {
268
269 if (blit_data_.texture_id == 0) {
270 return;
271 }
272 setup_gl();
273 BlitProgram::instance().draw(dst_rect, 0.f, texture, mask, blend, BlendMode::UseAlpha);
274 }
275
do_blit_monochrome(const Rectf & dst_rect,const BlitData & texture,const RGBAColor & blend)276 void Texture::do_blit_monochrome(const Rectf& dst_rect,
277 const BlitData& texture,
278 const RGBAColor& blend) {
279 if (blit_data_.texture_id == 0) {
280 return;
281 }
282 setup_gl();
283 BlitProgram::instance().draw_monochrome(dst_rect, 0.f, texture, blend);
284 }
285
do_draw_line_strip(std::vector<DrawLineProgram::PerVertexData> vertices)286 void Texture::do_draw_line_strip(std::vector<DrawLineProgram::PerVertexData> vertices) {
287 if (blit_data_.texture_id == 0) {
288 return;
289 }
290 setup_gl();
291 DrawLineProgram::instance().draw(
292 {DrawLineProgram::Arguments{vertices, 0.f, BlendMode::UseAlpha}});
293 }
294
do_fill_rect(const Rectf & dst_rect,const RGBAColor & color,BlendMode blend_mode)295 void Texture::do_fill_rect(const Rectf& dst_rect, const RGBAColor& color, BlendMode blend_mode) {
296 if (blit_data_.texture_id == 0) {
297 return;
298 }
299 setup_gl();
300 FillRectProgram::instance().draw(dst_rect, 0.f, color, blend_mode);
301 }
302
blit_data() const303 const BlitData& Texture::blit_data() const {
304 return blit_data_;
305 }
306