Not logged inGosu Forums
Forum back to Help Search Register Login
Up Topic Gosu / Gosu Exchange / primitive drawing method in record block causes segfault
- - By erisdiscord Date 2011-03-01 07:44
Yeah, got an interesting problem.

I was trying to implement a cached drawing system so complex but infrequently changing drawing operations could be compiled to Gosu macros inline when I hit a bit of a wall.

Seems using a primitive drawing method (draw_line, draw_quad, draw_triangle) inside of a record block causes one of those new fangled dreaded segmentation faults the kids are always talking about. There's some weirdness with alpha transparency too, but the crashing bug is a bit more of a big deal.

Let me show you:

require 'gosu'

class Window < Gosu::Window
  def initialize
    super 480, 240, false
    @draw_cache = { }
    @image = self, 'test.png', false
    @font  = self, 'Helvetica', 16
  def draw
    draw_cache(:peachy_keen) do
      # Appears to work without a hitch.
      @font.draw "Hello, World!", 64, 0, 0
    draw_cache(:reasonably_fine) do
      # Transparency is rendered white even though it works as expected
      # outside of a recorded macro.
      @image.draw 64, 16, 0
    draw_cache(:this_causes_an_error) do
      draw_quad( 0,  0, Gosu::Color::WHITE,
                 0, 64, Gosu::Color::WHITE,
                64, 64, Gosu::Color::WHITE,
                64,  0, Gosu::Color::WHITE, 0)
  def draw_cache id, z = 0, &block
    unless @draw_cache.include? id
      @draw_cache[id] = record(&block)
    @draw_cache[id].draw 0, 0, z

This is the stack trace, complete with a mysteriously empty "C level backtrace information" section:

crash.rb:38: [BUG] Segmentation fault
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-darwin10.6.0]

-- control frame ----------
c:0008 p:---- s:0025 b:0025 l:000024 d:000024 CFUNC  :record
c:0007 p:0037 s:0022 b:0020 l:000019 d:000019 METHOD crash.rb:38
c:0006 p:0037 s:0014 b:0014 l:000218 d:000218 METHOD crash.rb:26
c:0005 p:---- s:0011 b:0011 l:000010 d:000010 FINISH
c:0004 p:---- s:0009 b:0009 l:000008 d:000008 CFUNC  :show
c:0003 p:0051 s:0006 b:0006 l:0018b8 d:0014a8 EVAL   crash.rb:45
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH
c:0001 p:0000 s:0002 b:0002 l:0018b8 d:0018b8 TOP  
-- Ruby level backtrace information ----------------------------------------
crash.rb:45:in <main>'
crash.rb:26:in draw'
crash.rb:38:in `record'

-- C level backtrace information -------------------------------------------

Is that weird or what? Or is it just me?
Parent - - By erisdiscord Date 2011-03-01 07:48
Also, since it's not clear from the example given: the crash occurs after the block exits, probably during the building of the vertex array. I confirmed using simple print statements; everything after the offending function call is executed just fine, but then there's the crash.

That should hopefully narrow down the source of the problem a little.
Parent - - By jlnr (dev) Date 2011-03-01 10:42
Oh well, the recording feature is not really finished in several ways. Proper Z-ordering is still in SVN (not released), that transparency bug is new to me :(, and drawing primitives should probably throw an exception for now. Sneaking them into a vertex array is not rocket science but will take some more code.

Development is still going sloooooooow...
Parent - - By erisdiscord Date 2011-03-01 23:16
Yeah, I suppose the absence of record from the documentation should have tipped me off. I've just noticed that the alpha for fonts is kind of messed up too, although it's not entirely missing like the image alpha.

This has potential to be an extremely useful feature. Seems like it would be a great way to implement the behind-the-scenes caching that Chingu::Text currently does using Image#from_text.

Since we're on the subject, though: are there any plans for macros to be saveable in the future like images are? It would be pretty awesome to be able to save them to files or even convert them to real raster images. Supposedly it's not too difficult to render to an OpenGL texture, although I can't say I know anything for sure.
Parent - - By Spooner Date 2011-03-02 00:26
See Texplay and its Window.render_to_image method (creates a block in which drawing ops get to an image; sort of a hack, but works in many use-cases)
Parent - - By erisdiscord Date 2011-03-02 06:30
Funny you should mention texplay—I just started playing with it this evening. I managed to cause several mysterious segfaults and a series of exceptions pertaining to unexpected nils, attempting to dup false and even invoking methods on garbage collected objects! ...none of which can I reproduce since they seem to have mysteriously stopped after I changed a couple of completely unrelated lines of code. Actually, most of those errors were found by changing seemingly unrelated code and I almost suspect that which one I got depended entirely on what memory addresses the objects were at or what kind of shoes the stack pointer was wearing. And I think that means I should have gone to bed a couple of hours ago.

I think the gen_eval thing is a little too magical. I really like the library (banister's a clever fellow), but I'd rather have to write something like image.paint { |gfx| gfx.fill 0, 0, :color => :red } if it meant I wouldn't have clever code creating hidden instance variables with illegal names and doing unspeakable things to my objects' implementation details. :D
Parent - - By banister Date 2011-03-02 07:04 Edited 2011-03-02 07:24
Do not use paint!!! :) im retiring it, yes it does some crazy shit, best to forget abou tit

Just do: image.fill x, y, :color => blah. Invoke all drawing actions directly on the image. paint is dead to me now (i should probalby tell other people)

EDIT: the version of gen_eval( used in texplay is an old one, i have subsequently fixed many of the bugs and brought in 1.9 compatibility (which the version in texplay doesn't properly hvae) however there is still a ridiculous edge case in 1.9 due to the annoying way ivars for objects are implemented, that i cannot get around (it doesn't result in segfaults it just results in weird behaviour). This is really sad as gen_eval was so close to being brilliant, and it can work perfectly in ruby 1.8.

There are a few options here:

(1) To use local_eval instead - the only difference between gen_eval and local_eval is that local_eval is not threadsafe (https://githubcom/banister/local_eval), oh and local_eval doesn't cause crashes (or at least it shouldn't as it's a much simpler concept)
(2)  To replace paint with a simple instance_eval/yield
(3) Get drunk
Parent - - By erisdiscord Date 2011-03-02 15:08
Ach, fair enough! The guide still indicates that using paint { ... } for multiple operations is faster, but I assume that's out of date now. Great library, by the way, other than that gen_eval weirdness. :)

I'd like to know more about that edge case meanwhile, just for curiosity's sake. How are ivars implemented in Ruby 1.9 and how is it different to 1.8?
Parent - By banister Date 2011-03-02 17:03
yeah the guide is really out of date :/ there's a lot of new functionality i havent documented (except in the texplay thread in the forums)

Regarding the edge-case; 1.8 ivars are just stored in a hashtable. The way that gen_eval (in 1.8) works is by duplicating the context of the paint block (block.binding.eval('self')) but having the ivar table pointer of the dup point to the ivar table of the original context. This ensures that any modifications to ivars inside the block persist outside it.

The difference in 1.9 is two-fold, first that there are a small number of ivars that are actually embedded in the object itself (not stored in an external ivar table) secondly that a record is kept of the number of ivars that exist in the object.

The first issue can be gotten around by overloading the object with ivars (>3) so that the object is forced to use an external ivar table -- an external ivar table is required as we can then get our dupped context's iv table pointer to point to it (this is not possible with embedded ivars). However, the second issue, the fact that a record is kept of the number of ivars cannot be gotten around. Things will work fine inside the block, but if new ivars are added inside the block these will only be registered by the dup but not by the original context. Yet the ivar table itself will be updated with the new ivars in the original. This will cause unexpected/strange behaviour as the number of ivars in the table will not match the number of ivars it expects.

local_eval works differently and doesnt suffer this problem as it doesn't dup the context but uses it directly so the number of ivars in the table will always match the ivar count. However the whole purpose of the dup wsa to make everthing threadsafe. local_eval is therefore not threadsafe but aside from that should work fine. gen_eval/local_eval both work by mixing in modules/classes (yes classes hehehe) into the block context to provide functionality for the duration of the block. gen_eval doesn't need to unmix the classes after the block is executed as it's a dup and can just be discarded, but local_eval needs to unmix the classes after the block and this is why it's not threadsafe as one thread may see classes mixed in when it shouldn't and so get access to functionality it should not.

In summary tho, as to the edge case- the edge case in 1.9 gen_eval only occurs when new ivars are created inside the block. This is likely a rare occurance but nonetheless is a limitation and so unfortunately gen_eval is dead in 1.9, maybe it can be ressurected in a later ruby version.
Parent - - By jlnr (dev) Date 2011-03-02 03:31

> Yeah, I suppose the absence of record from the documentation should have tipped me off. I've just noticed that the alpha for fonts is kind of messed up too, although it's not entirely missing like the image alpha.

Hmm, can you provide an example screenshot illustrating this?

> are there any plans for macros to be saveable in the future like images are?

Nope. Macros and render-to-texture are two distinct things. Macros are easy, super-fast to create and almost impossible to store, render-to-texture is harder to implement, has a higher cost and makes saving easy. Also, macros do not lose information unlike render-to-texture which pixelates when you stretch it afterwards. The semantics for alpha differ too. If render-to-texture makes it into Gosu, it will be a separate function (render{}).
Parent - - By erisdiscord Date 2011-03-02 06:03 Edited 2011-03-02 06:10

> an example screenshot

The problem's even more complex than I thought—It seems to mysteriously self-correct if anything non-macro has ever been drawn. Could you have overlooked some important OpenGL state setup in the vertex array drawing code? Here's a demo program that lets you see the effect more easily, with screen shots for good measure.

I can't be sure, but the transparent part is probably turned opaque black too. Rendering images like this under the same circumstances seems to draw the transparent parts without the alpha channel, so the "true" color of the pixels shows through. I can post a screen shot tomorrow when I'm not about to fall asleep.

My discovery is a total edge case; I doubt anyone would ever encounter it in the wild unless they were writing test code like mine. Totally weird behavior.
Attachment: damn-macros.rb.txt - Demo program shown in the screen shots. Press space to switch between macro and draw_text. (703B)
Parent - By jlnr (dev) Date 2011-03-05 08:08
It was the lack of GL_BLEND & GL_BLEND_MODE being set. There was also a new bug introduced by a combination of patches, but now your sample works as expected here :) Progress!
Parent - By erisdiscord Date 2011-03-02 06:17
Oh yes, and what I actually meant was "are there any plans to allow macros to be rendered to textures?" to which you have indirectly answered yes. Or rather, I assume that render { @macro.draw x, y, z } will work just as well as render { @image.draw x, y, z } once you have implemented it. :)

When you do add it, I think that render should accept height and width arguments for the image to render. They should be optional, where the default is whatever the window size is. I'd get the most use out of it if areas where the black background show through were transparent on the product, but either way it would be a pretty powerful feature. :D
Up Topic Gosu / Gosu Exchange / primitive drawing method in record block causes segfault

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill