Class: Zif::RenderTarget
- Inherits:
-
Object
- Object
- Zif::RenderTarget
- Includes:
- Serializable
- Defined in:
- lib/zif/render_target.rb
Overview
For creating and updating Render Targets.
A render target in DRGTK is a way to programmatically create a static image out of sprites. It acts just like $gtk.args.outputs in that it accepts an array of sprites
and other primitives
. It gets rendered into memory at the end of the tick where it is referenced out of $gtk.args.outputs, based on its contents. To display the result, you need to send $gtk.args.outputs a sprite which references the name of the render target as its path
.
This class holds references to the #sprites and all of the configuration options necessary to invoke this concept in DragonRuby GTK. It also includes a Sprite referencing the created image in #containing_sprite.
Once set up, you can force DRGTK to fully render the image using #redraw, which redraws everything.
Although an already rendered RenderTarget is cheap to display, one drawback of using RenderTargets is that they take a little longer to process in the first place, compared to simply drawing sprites the normal way. This can become an issue if you need to frequently update the RenderTarget due to changes on the source sprites. Say you have a large tile map you pregenerate as a RenderTarget when the game loads. If you need to change a single tile, like if that tile represents a door and the door opens, normally you would need to regenerate the entire RenderTarget using all of the source sprites.
A technique the DragonRuby community (specifically Islacrusez, oeloeloel) has identified to overcome this performance issue is to build another RenderTarget using the previously rendered one, plus whatever sprites are changing. See this example implementation of this technique: github.com/oeloeloel/persistent-outputs
This strategy is implemented here by #redraw_from_buffer. Since this is inherently an additive process, #redraw_from_buffer allows you to cut out a single rectangle (via the cut_rect
param) from the old image to handle deletions.
See DRGTK docs on Render Targets docs.dragonruby.org/#—-advanced-rendering—simple-render-targets—main.rb
Direct Known Subclasses
Instance Attribute Summary collapse
-
#bg_color ⇒ Symbol, Array<Integer>
Accepts
:black
,:white
, or an RGBA color array like [255,255,255,255]. -
#height ⇒ Integer
The total height of the rendered image.
-
#labels ⇒ Array<Zif::UI::Label>
The list of labels this RenderTarget is rendering.
-
#name ⇒ String, Symbol
The name of the render target.
-
#primitives ⇒ Array<Zif::Sprite, Hash>
The list of other primitives this RenderTarget is rendering.
-
#sprites ⇒ Array<Zif::Sprite>
The list of sprites this RenderTarget is rendering.
-
#width ⇒ Integer
The total width of the rendered image.
-
#z_index ⇒ Integer
When comparing the containing sprite with other sprites, this sets the layering order.
1. Public Interface collapse
-
#clicked?(point, kind = :up) ⇒ Object?
This implements functionality for the Services::InputService to check for click handlers amongst the list of #sprites and #primitives assigned to this object.
-
#containing_sprite ⇒ Zif::Sprite
A sprite which references this render target as its
path
, and set to the samewidth
andheight
by default. - #exclude_from_serialize ⇒ Object
-
#initialize(name, bg_color: :black, width: 1, height: 1, z_index: 0) ⇒ RenderTarget
constructor
A new instance of RenderTarget.
-
#project_from(rect = {}) ⇒ Object
Reassigns the
source_x
,source_y
,source_w
andsource_h
of the #containing_sprite. -
#project_to(rect = {}) ⇒ Object
Reassigns the x, y, width and height of the #containing_sprite.
-
#redraw ⇒ Object
Call this method when you want to actually draw all #sprites onto the rendered image.
-
#redraw_from_buffer(sprites: [], cut_rect: nil, additional_containing_sprites: []) ⇒ Object
Double Buffering.
-
#relative_point(point) ⇒ Array<Integer>
Convert the positional [x, y] array
point
from the screen coordinates, to the relative coordinates inside the #containing_sprite. - #resize(w, h) ⇒ Object
2. Private-ish methods collapse
-
#cut_containing_sprites(rect) ⇒ Array<Hash<Symbol, Numeric>>
private
. right v @height ->
------
—+ | 2 | | | | 3 | top ->----
-+ | | |r| | | 1-
—+ <- bottom | | 4 | | | | 0,0 ->----
—–+ ^ ^ left @width This creates four sprite-hashes to capture the area outside of a rectangle, used in #redraw_from_buffer. -
#full_containing_sprite ⇒ Object
private
Like #containing_sprite, returns a Zif::Sprite referencing #name as
path
at the full width. - #set_inactive_buffer_name ⇒ Object private
-
#switch_buffer(additional_containing_sprites = []) ⇒ Object
private
Switch the #containing_sprite (and optional additional containing sprites)
path
to the inactive buffer Swap names so the name is the inactive name and vice versa.
Methods included from Serializable
Constructor Details
#initialize(name, bg_color: :black, width: 1, height: 1, z_index: 0) ⇒ RenderTarget
Returns a new instance of RenderTarget.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/zif/render_target.rb', line 82 def initialize(name, bg_color: :black, width: 1, height: 1, z_index: 0) @name = name @width = width @height = height @z_index = z_index @sprites = [] @labels = [] @primitives = [] # This could probably be improved with a Color module @bg_color = case bg_color when :black [0, 0, 0, 0] when :white [255, 255, 255, 0] else bg_color end end |
Instance Attribute Details
#bg_color ⇒ Symbol, Array<Integer>
Returns Accepts :black
, :white
, or an RGBA color array like [255,255,255,255].
60 61 62 |
# File 'lib/zif/render_target.rb', line 60 def bg_color @bg_color end |
#height ⇒ Integer
Returns The total height of the rendered image. This could be larger or smaller than the size used to display the image on the #containing_sprite.
57 58 59 |
# File 'lib/zif/render_target.rb', line 57 def height @height end |
#labels ⇒ Array<Zif::UI::Label>
Returns The list of labels this RenderTarget is rendering.
66 67 68 |
# File 'lib/zif/render_target.rb', line 66 def labels @labels end |
#name ⇒ String, Symbol
Returns The name of the render target. This is used when invoking the render target in DRGTK, so this must be unique.
49 50 51 |
# File 'lib/zif/render_target.rb', line 49 def name @name end |
#primitives ⇒ Array<Zif::Sprite, Hash>
Returns The list of other primitives this RenderTarget is rendering.
69 70 71 |
# File 'lib/zif/render_target.rb', line 69 def primitives @primitives end |
#sprites ⇒ Array<Zif::Sprite>
Returns The list of sprites this RenderTarget is rendering.
63 64 65 |
# File 'lib/zif/render_target.rb', line 63 def sprites @sprites end |
#width ⇒ Integer
Returns The total width of the rendered image. This could be larger or smaller than the size used to display the image on the #containing_sprite.
53 54 55 |
# File 'lib/zif/render_target.rb', line 53 def width @width end |
#z_index ⇒ Integer
Returns When comparing the containing sprite with other sprites, this sets the layering order.
72 73 74 |
# File 'lib/zif/render_target.rb', line 72 def z_index @z_index end |
Instance Method Details
#clicked?(point, kind = :up) ⇒ Object?
This implements functionality for the Services::InputService to check for click handlers amongst the list of #sprites and #primitives assigned to this object. It will adjust the point
from the screen itself to the relative position of the #sprites within the #containing_sprite, and send this adjusted point to the #sprites #clicked?
method.
163 164 165 166 167 168 169 |
# File 'lib/zif/render_target.rb', line 163 def clicked?(point, kind=:up) relative = relative_point(point) # puts "#{self.class.name}:#{name}: clicked? #{point} -> relative #{relative}" find_and_return = ->(sprite) { sprite.respond_to?(:clicked?) && sprite.clicked?(relative, kind) } @sprites.reverse_each.find(&find_and_return) || @primitives.reverse_each.find(&find_and_return) end |
#containing_sprite ⇒ Zif::Sprite
Returns A sprite which references this render target as its path
, and set to the same width
and height
by default.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/zif/render_target.rb', line 103 def containing_sprite return @containing_sprite if @containing_sprite redraw @containing_sprite = Zif::Sprite.new.tap do |s| s.name = "rt_#{@name}_containing_sprite" s.x = 0 s.y = 0 s.z_index = @z_index s.w = @width s.h = @height s.path = @name s.source_x = 0 s.source_y = 0 s.source_w = @width s.source_h = @height s.render_target = self end end |
#cut_containing_sprites(rect) ⇒ Array<Hash<Symbol, Numeric>>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
.
right
v
@height -> +------+---+
| 2 | |
| | 3 |
top -> +----+-+ |
| |r| |
| 1 +-+---+ <- bottom
| | 4 |
| | |
0,0 -> +----+-----+
^ ^
left @width
This creates four sprite-hashes to capture the area outside of a rectangle, used in #redraw_from_buffer
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/zif/render_target.rb', line 295 def cut_containing_sprites(rect) left = rect[0] bottom = rect[1] right = left + rect[2] top = bottom + rect[3] [ # 1 { x: 0, source_x: 0, y: 0, source_y: 0, w: left, source_w: left, h: top, source_h: top, path: @name }, # 2 { x: 0, source_x: 0, y: top, source_y: top, w: right, source_w: right, h: @height - top, source_h: @height - top, path: @name }, # 3 { x: right, source_x: right, y: bottom, source_y: bottom, w: @width - right, source_w: @width - right, h: @height - bottom, source_h: @height - bottom, path: @name }, # 4 { x: left, source_x: left, y: 0, source_y: 0, w: @width - left, source_w: @width - left, h: bottom, source_h: bottom, path: @name } ] end |
#exclude_from_serialize ⇒ Object
204 205 206 |
# File 'lib/zif/render_target.rb', line 204 def exclude_from_serialize %w[sprites primitives] end |
#full_containing_sprite ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Like #containing_sprite, returns a Zif::Sprite referencing #name as path
at the full width. This is private because it’s used internally in #redraw_from_buffer, it should not be modified externally
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/zif/render_target.rb', line 356 def full_containing_sprite return @full_containing_sprite if @full_containing_sprite @full_containing_sprite = Zif::Sprite.new.tap do |s| s.name = "rt_#{@name}_full_containing_sprite" s.x = 0 s.y = 0 s.z_index = @z_index s.w = @width s.h = @height s.path = @name s.source_x = 0 s.source_y = 0 s.source_w = @width s.source_h = @height s.render_target = self end end |
#project_from(rect = {}) ⇒ Object
Reassigns the source_x
, source_y
, source_w
and source_h
of the #containing_sprite.
195 196 197 198 199 200 201 |
# File 'lib/zif/render_target.rb', line 195 def project_from(rect={}) containing_sprite.assign( containing_sprite.source_rect_hash.merge( Zif::Sprite.rect_hash_to_source_hash(rect) ) ) end |
#project_to(rect = {}) ⇒ Object
Reassigns the x, y, width and height of the #containing_sprite.
187 188 189 190 191 |
# File 'lib/zif/render_target.rb', line 187 def project_to(rect={}) containing_sprite.assign( containing_sprite.rect_hash.merge(rect) ) end |
#redraw ⇒ Object
Call this method when you want to actually draw all #sprites onto the rendered image. You will need to redraw if you want any changes made to #sprites to be reflected in the #containing_sprite. This works by asking DRGTK for a reference to the render target by name, and resetting the #width and #height. DRGTK detects this and performs the render at the end of the tick. Therefore, please do not attempt to reference this render target directly via $gtk.args outside of this class - it will cause the render target to be redrawn without directing it to draw the sprites contained here.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/zif/render_target.rb', line 136 def redraw # puts "RenderTarget#redraw: #{@name} #{@width} #{@height} #{@sprites.length} sprites, #{@labels.length} labels" # $services&.named(:tracer)&..mark("#redraw: #{@name} Begin") targ = $gtk.args.outputs[@name] targ.width = @width targ.height = @height # It's important to set the background color intentionally. Even if alpha == 0, semi-transparent images in # render targets will pick up this color as an additive. Usually you want black. targ.background_color = @bg_color targ.primitives << @primitives if @primitives&.any? targ.sprites << @sprites if @sprites&.any? targ.labels << @labels if @labels&.any? # $services&.named(:tracer)&..mark("#redraw: #{@name} Complete") end |
#redraw_from_buffer(sprites: [], cut_rect: nil, additional_containing_sprites: []) ⇒ Object
Need some examples of double buffering
Double Buffering
This method is for drawing new sprites on top of an already rendered RT, and optionally cut out a rectangle from the RT (for deletion) You might want to use this if your list of #sprites for the RT is very large, so rendering all of the sprites is slow, and you just need to modify a small subset.
The classic example is a paint application, where each tick adds a new brush stroke to the canvas. Instead of letting the #sprites array grow unbounded and redrawing all of them for each stroke, simply take the previous canvas and draw a new sprite on top of it.
A more complex example is if a single tile is changing inside of a large tile map. Instead of redrawing every tile, delete the tile which is changing from the already rendered target, and then redraw just the changing tile.
One drawback of using this is that you are forced to specify a single rectangle area which is changing.
Another drawback to using this is that the path
for any sprite referencing the RenderTarget needs to change every time the buffer is switched. Since this references it’s own containing sprite, we can update the path for that sprite automatically, but if there exist any additional sprites referencing the path
(like if you have a map and a minimap pointing to the same path
), those will have to be updated manually. To support this, you can pass additional containing sprites as an array to have their path
updated.
See the ExampleApp::DoubleBufferRenderTest scene for a working example.
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/zif/render_target.rb', line 239 def redraw_from_buffer(sprites: [], cut_rect: nil, additional_containing_sprites: []) # $services&.named(:tracer)&..mark("RenderTarget#redraw_from_buffer: #{@name} Begin") source_buffer_sprites = cut_rect ? cut_containing_sprites(cut_rect) : [full_containing_sprite] set_inactive_buffer_name unless @inactive_buffer_name # puts "RenderTarget#redraw_from_buffer: name: #{@name}, inactive: #{@inactive_buffer_name}" # puts "RenderTarget#redraw_from_buffer: cut_rect: #{cut_rect}, source: #{source_buffer_sprites.inspect}" # $services&.named(:tracer)&..mark("RenderTarget#redraw_from_buffer: #{@name} Sprites") targ = $gtk.args.outputs[@inactive_buffer_name] targ.width = @width targ.height = @height # $services&.named(:tracer)&..mark("RenderTarget#redraw_from_buffer: #{@name} HW") targ.background_color = @bg_color targ.sprites << [source_buffer_sprites] + sprites switch_buffer(additional_containing_sprites) # $services&.named(:tracer)&..mark("RenderTarget#redraw_from_buffer: #{@name} End") end |
#relative_point(point) ⇒ Array<Integer>
Convert the positional [x, y] array point
from the screen coordinates, to the relative coordinates inside the #containing_sprite
175 176 177 178 179 180 181 182 183 |
# File 'lib/zif/render_target.rb', line 175 def relative_point(point) Zif.add_positions( Zif.sub_positions( point, # FIXME??: Zif.position_math(:mult, point, containing_sprite.source_wh), containing_sprite.xy ), containing_sprite.source_xy ) end |
#resize(w, h) ⇒ Object
125 126 127 128 |
# File 'lib/zif/render_target.rb', line 125 def resize(w, h) @width = w @height = h end |
#set_inactive_buffer_name ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
263 264 265 |
# File 'lib/zif/render_target.rb', line 263 def set_inactive_buffer_name @inactive_buffer_name = "#{@name}_buf" end |
#switch_buffer(additional_containing_sprites = []) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Switch the #containing_sprite (and optional additional containing sprites) path
to the inactive buffer Swap names so the name is the inactive name and vice versa. Private, you should probably not call this directly, it is called automatically by #redraw_from_buffer
272 273 274 275 |
# File 'lib/zif/render_target.rb', line 272 def switch_buffer(additional_containing_sprites=[]) ([containing_sprite] + additional_containing_sprites).each { |cs| cs.path = @inactive_buffer_name } @inactive_buffer_name, @name = @name, @inactive_buffer_name end |