1// SuperTux
2// Copyright (C) 2016 Ingo Ruhnke <grumbel@gmail.com>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (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, see <http://www.gnu.org/licenses/>.
16
17#include "video/canvas.hpp"
18
19#include <algorithm>
20
21#include "supertux/globals.hpp"
22#include "util/log.hpp"
23#include "util/obstackpp.hpp"
24#include "video/drawing_request.hpp"
25#include "video/painter.hpp"
26#include "video/renderer.hpp"
27#include "video/surface.hpp"
28#include "video/video_system.hpp"
29
30Canvas::Canvas(DrawingContext& context, obstack& obst) :
31 m_context(context),
32 m_obst(obst),
33 m_requests()
34{
35}
36
37Canvas::~Canvas()
38{
39 clear();
40}
41
42void
43Canvas::clear()
44{
45 for (auto& request : m_requests)
46 {
47 request->~DrawingRequest();
48 }
49 m_requests.clear();
50}
51
52void
53Canvas::render(Renderer& renderer, Filter filter)
54{
55 // On a regular level, each frame has around 50-250 requests (before
56 // batching it was 1000-3000), the sort comparator function is
57 // called approximatly 3-7 times for each request.
58 std::stable_sort(m_requests.begin(), m_requests.end(),
59 [](const DrawingRequest* r1, const DrawingRequest* r2){
60 return r1->layer < r2->layer;
61 });
62
63 Painter& painter = renderer.get_painter();
64
65 for (const auto& i : m_requests) {
66 const DrawingRequest& request = *i;
67
68 if (filter == BELOW_LIGHTMAP && request.layer >= LAYER_LIGHTMAP)
69 continue;
70 else if (filter == ABOVE_LIGHTMAP && request.layer <= LAYER_LIGHTMAP)
71 continue;
72
73 switch (request.type) {
74 case TEXTURE:
75 painter.draw_texture(static_cast<const TextureRequest&>(request));
76 break;
77
78 case GRADIENT:
79 painter.draw_gradient(static_cast<const GradientRequest&>(request));
80 break;
81
82 case FILLRECT:
83 painter.draw_filled_rect(static_cast<const FillRectRequest&>(request));
84 break;
85
86 case INVERSEELLIPSE:
87 painter.draw_inverse_ellipse(static_cast<const InverseEllipseRequest&>(request));
88 break;
89
90 case LINE:
91 painter.draw_line(static_cast<const LineRequest&>(request));
92 break;
93
94 case TRIANGLE:
95 painter.draw_triangle(static_cast<const TriangleRequest&>(request));
96 break;
97
98 case GETPIXEL:
99 painter.get_pixel(static_cast<const GetPixelRequest&>(request));
100 break;
101 }
102 }
103}
104
105void
106Canvas::draw_surface(const SurfacePtr& surface,
107 const Vector& position, float angle, const Color& color, const Blend& blend,
108 int layer)
109{
110 if (!surface) return;
111
112 const auto& cliprect = m_context.get_cliprect();
113
114 // discard clipped surface
115 if (position.x > cliprect.get_right() ||
116 position.y > cliprect.get_bottom() ||
117 position.x + static_cast<float>(surface->get_width()) < cliprect.get_left() ||
118 position.y + static_cast<float>(surface->get_height()) < cliprect.get_top())
119 return;
120
121 auto request = new(m_obst) TextureRequest();
122
123 request->type = TEXTURE;
124 request->layer = layer;
125 request->flip = m_context.transform().flip ^ surface->get_flip();
126 request->alpha = m_context.transform().alpha;
127 request->blend = blend;
128
129 request->srcrects.emplace_back(Rectf(surface->get_region()));
130 request->dstrects.emplace_back(Rectf(apply_translate(position), Size(surface->get_width(), surface->get_height())));
131 request->angles.emplace_back(angle);
132 request->texture = surface->get_texture().get();
133 request->displacement_texture = surface->get_displacement_texture().get();
134 request->color = color;
135
136 m_requests.push_back(request);
137}
138
139void
140Canvas::draw_surface(const SurfacePtr& surface, const Vector& position, int layer)
141{
142 draw_surface(surface, position, 0.0f, Color(1.0f, 1.0f, 1.0f), Blend(), layer);
143}
144
145void
146Canvas::draw_surface_scaled(const SurfacePtr& surface, const Rectf& dstrect,
147 int layer, const PaintStyle& style)
148{
149 draw_surface_part(surface, Rectf(0.0f, 0.0f, static_cast<float>(surface->get_width()), static_cast<float>(surface->get_height())),
150 dstrect, layer, style);
151}
152
153void
154Canvas::draw_surface_part(const SurfacePtr& surface, const Rectf& srcrect, const Rectf& dstrect,
155 int layer, const PaintStyle& style)
156{
157 if (!surface) return;
158
159 auto request = new(m_obst) TextureRequest();
160
161 request->type = TEXTURE;
162 request->layer = layer;
163 request->flip = m_context.transform().flip ^ surface->get_flip();
164 request->alpha = m_context.transform().alpha * style.get_alpha();
165 request->blend = style.get_blend();
166
167 request->srcrects.emplace_back(srcrect);
168 request->dstrects.emplace_back(apply_translate(dstrect.p1()), dstrect.get_size());
169 request->angles.emplace_back(0.0f);
170 request->texture = surface->get_texture().get();
171 request->displacement_texture = surface->get_displacement_texture().get();
172 request->color = style.get_color();
173
174 m_requests.push_back(request);
175}
176
177void
178Canvas::draw_surface_batch(const SurfacePtr& surface,
179 std::vector<Rectf> srcrects,
180 std::vector<Rectf> dstrects,
181 const Color& color,
182 int layer)
183{
184 const size_t len = srcrects.size();
185 draw_surface_batch(surface,
186 std::move(srcrects),
187 std::move(dstrects),
188 std::vector<float>(len, 0.0f),
189 color, layer);
190}
191
192void
193Canvas::draw_surface_batch(const SurfacePtr& surface,
194 std::vector<Rectf> srcrects,
195 std::vector<Rectf> dstrects,
196 std::vector<float> angles,
197 const Color& color,
198 int layer)
199{
200 if (!surface) return;
201
202 auto request = new(m_obst) TextureRequest();
203
204 request->type = TEXTURE;
205 request->layer = layer;
206 request->flip = m_context.transform().flip ^ surface->get_flip();
207 request->alpha = m_context.transform().alpha;
208 request->color = color;
209
210 request->srcrects = std::move(srcrects);
211 request->dstrects = std::move(dstrects);
212 request->angles = std::move(angles);
213
214 for (auto& dstrect : request->dstrects)
215 {
216 dstrect = Rectf(apply_translate(dstrect.p1()), dstrect.get_size());
217 }
218
219 request->texture = surface->get_texture().get();
220 request->displacement_texture = surface->get_displacement_texture().get();
221
222 m_requests.push_back(request);
223}
224
225void
226Canvas::draw_text(const FontPtr& font, const std::string& text,
227 const Vector& pos, FontAlignment alignment, int layer, const Color& color)
228{
229 font->draw_text(*this, text, pos, alignment, layer, color);
230}
231
232void
233Canvas::draw_center_text(const FontPtr& font, const std::string& text,
234 const Vector& position, int layer, const Color& color)
235{
236 draw_text(font, text, Vector(position.x + static_cast<float>(m_context.get_width()) / 2.0f, position.y),
237 ALIGN_CENTER, layer, color);
238}
239
240void
241Canvas::draw_gradient(const Color& top, const Color& bottom, int layer,
242 const GradientDirection& direction, const Rectf& region,
243 const Blend& blend)
244{
245 auto request = new(m_obst) GradientRequest();
246
247 request->type = GRADIENT;
248 request->layer = layer;
249
250 request->flip = m_context.transform().flip;
251 request->alpha = m_context.transform().alpha;
252 request->blend = blend;
253
254 request->top = top;
255 request->bottom = bottom;
256 request->direction = direction;
257 request->region = Rectf(apply_translate(region.p1()),
258 apply_translate(region.p2()));
259
260 m_requests.push_back(request);
261}
262
263void
264Canvas::draw_filled_rect(const Rectf& rect, const Color& color,
265 int layer)
266{
267 draw_filled_rect(rect, color, 0.0f, layer);
268}
269
270void
271Canvas::draw_filled_rect(const Rectf& rect, const Color& color, float radius, int layer)
272{
273 auto request = new(m_obst) FillRectRequest;
274
275 request->type = FILLRECT;
276 request->layer = layer;
277
278 request->flip = m_context.transform().flip;
279 request->alpha = m_context.transform().alpha;
280
281 request->rect = Rectf(apply_translate(rect.p1()),
282 rect.get_size());
283 request->color = color;
284 request->color.alpha = color.alpha * m_context.transform().alpha;
285 request->radius = radius;
286
287 m_requests.push_back(request);
288}
289
290void
291Canvas::draw_inverse_ellipse(const Vector& pos, const Vector& size, const Color& color, int layer)
292{
293 auto request = new(m_obst) InverseEllipseRequest;
294
295 request->type = INVERSEELLIPSE;
296 request->layer = layer;
297
298 request->flip = m_context.transform().flip;
299 request->alpha = m_context.transform().alpha;
300
301 request->pos = apply_translate(pos);
302 request->color = color;
303 request->color.alpha = color.alpha * m_context.transform().alpha;
304 request->size = size;
305
306 m_requests.push_back(request);
307}
308
309void
310Canvas::draw_line(const Vector& pos1, const Vector& pos2, const Color& color, int layer)
311{
312 auto request = new(m_obst) LineRequest;
313
314 request->type = LINE;
315 request->layer = layer;
316
317 request->flip = m_context.transform().flip;
318 request->alpha = m_context.transform().alpha;
319
320 request->pos = apply_translate(pos1);
321 request->color = color;
322 request->color.alpha = color.alpha * m_context.transform().alpha;
323 request->dest_pos = apply_translate(pos2);
324
325 m_requests.push_back(request);
326}
327
328void
329Canvas::draw_triangle(const Vector& pos1, const Vector& pos2, const Vector& pos3, const Color& color, int layer)
330{
331 auto request = new(m_obst) TriangleRequest;
332
333 request->type = TRIANGLE;
334 request->layer = layer;
335
336 request->flip = m_context.transform().flip;
337 request->alpha = m_context.transform().alpha;
338
339 request->pos1 = apply_translate(pos1);
340 request->pos2 = apply_translate(pos2);
341 request->pos3 = apply_translate(pos3);
342 request->color = color;
343 request->color.alpha = color.alpha * m_context.transform().alpha;
344
345 m_requests.push_back(request);
346}
347
348void
349Canvas::get_pixel(const Vector& position, const std::shared_ptr<Color>& color_out)
350{
351 assert(color_out);
352
353 Vector pos = apply_translate(position);
354
355 // There is no light offscreen.
356 if (pos.x >= static_cast<float>(m_context.get_viewport().get_width()) ||
357 pos.y >= static_cast<float>(m_context.get_viewport().get_height()) ||
358 pos.x < 0.0f ||
359 pos.y < 0.0f)
360 {
361 *color_out = Color(0, 0, 0);
362 return;
363 }
364
365 auto request = new(m_obst) GetPixelRequest();
366
367 request->layer = LAYER_GETPIXEL;
368 request->pos = pos;
369 request->color_ptr = color_out;
370
371 m_requests.push_back(request);
372}
373
374Vector
375Canvas::apply_translate(const Vector& pos) const
376{
377 Vector translation = m_context.transform().translation;
378 return (pos - translation) + Vector(static_cast<float>(m_context.get_viewport().left),
379 static_cast<float>(m_context.get_viewport().top));
380}
381
382/* EOF */
383