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