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