• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Question - Code Anyway to change render targets in the middle of a view being drawn?

Hi! I'm wondering if I would be able to change the render target, or the view_surface_id to allow me to draw different layers of the scene to different surfaces. I'm doing something a bit similar to deferred rendering.

For example, I need to draw my lights on a seperate surface, so that entire surface can then be drawn on to the application surface with a blend mode. I've tried changing the view_surface_id right before the lights are drawn, and then change it back, but the view_surface_id only seems to really update after the view is finished drawing. I'm coming from GM1, but I've imported to GM2. I've tried just about everything in GM1 to get this effect, is there any added features of GM2 to let me do this?

I'm achieving this currently by offsetting all the absolute coordinates of the lights to local-surface coordinates, and taking into account the camera's zoom as well, but this seems hacky, and view rotation might make this a hastle, though I haven't tried to implement rotation yet.

I have other surfaces than just lights too, such as the background, a depth-map, and a distortion map.
The game is 2D, but I'm doing some pretty advanced graphics stuff. Lots of shaders and what-not.

Thanks for the help! :D
 

Mike

nobody important
GMC Elder
Render targets are stacked, so if one is set for a view, you can simply set another in an object/layer, then pop it off later. This would mean everything goes to the 2nd target, and not the view bound one.

Code:
   surface_set_target( surf1 );
   // do some drawing
        surface_set_target( surf2 );
        // do some other drawing
        surface_reset_target()
   surface_reset_target()
 

GMWolf

aka fel666
Render targets are stacked, so if one is set for a view, you can simply set another in an object/layer, then pop it off later. This would mean everything goes to the 2nd target, and not the view bound one.

Code:
   surface_set_target( surf1 );
   // do some drawing
        surface_set_target( surf2 );
        // do some other drawing
        surface_reset_target()
   surface_reset_target()
Wooaah! Really? How did I not know that?!? Pretty good to know!
 

Mike

nobody important
GMC Elder
Nope. Was changed when the application surface appeared to be more......useful :)

Also required as when users set a surface, it had to be able to "pop" back to the appsurface easily - as it's "just" another surface. Also deemed to be MUCH more useful.
 

GMWolf

aka fel666
Nope. Was changed when the application surface appeared to be more......useful :)

Also required as when users set a surface, it had to be able to "pop" back to the appsurface easily - as it's "just" another surface. Also deemed to be MUCH more useful.
Aah! makes sense! I can actually think of a few good uses!
 
I know about drawing to surfaces, but I need to draw to the surface as if it's the view surface. I need the coordinates and scaling to work with the view.

For instance, when I draw a light at 300,520... I need that to draw to the surface with the view in-mind... not actually 300,520 on the surface. I'm getting it to work right now by offsetting the view coordinates and scale with the instance coords and scale, but I'm just wondering if there's an easier way to do it.

I don't know how to explain this better :/

I'm trying to bind a view to a surface, in the MIDDLE of the draw event, then switch it back to the other surface.
 

Mike

nobody important
GMC Elder
You'll need to work out your own view matrix to offset the world - and scale if required. But once you do that, it'll render to the surface as required.
 
You'll need to work out your own view matrix to offset the world - and scale if required. But once you do that, it'll render to the surface as required.
Ok thanks for your help!
Is there any particular reason we can't change the view_surface_id in the middle of the draw event? I feel like this would be a good feature to implement.
 

Mike

nobody important
GMC Elder
because it's already been set at the start of the view.... imagine this...

Code:
     Pre-Draw() -> set render target()
     surface_id -> set_render_target()
       set_render_target()
           set_render_target()

           Draw stuff....
           change_surface_id     //  how would this work? which one would it change?
           Draw stuff....
           change_surface_id     //  how would this work? which one would it change?
           Draw stuff....
           Draw stuff....

           pop_render_target();
      pop_render_target();
    pop_render_target();
 pop_render_target();
Setting the ID half way through is way too late, especially if you're already deep into other targets. But, there isn't anything stopping you from setting another target and rendering everything yourself in these odd cases.
 
P

pieterator

Guest
I had this exact same problem recently. I wanted to render multiple layers to different surfaces so I could composite them later using masks and shaders. The solution I came up with was using the application_surface as a sort of temp surface.

So for my background layer_script_begin() I have something like:
Code:
//application_surface is active
draw_clear_alpha(c_black,0);
Then I let the contents of the layer draw normally to the application surface, and afterwards just capture the contents of the application surface with this layer_script_end() script:
Code:
surface_copy(background_surface,0,0,application_surface);
You then do this for each layer you want to capture in this way. Just remember to capture everything you want to keep before you call draw_clear_alpha(), since you are clearing the application surface every time.

This is not without its caveates however, as you will soon find out when you draw something transparent over something 100% opaque (due to how the normal blendmode calculates your new rgba values).
My best solution for this was to set the blend mode in the above layer_script_begin() to:
Code:
//application_surface is active
draw_clear_alpha(c_black,0);
gpu_set_blendmode_ext_sepalpha(bm_src_alpha,bm_inv_src_alpha,bm_inv_dest_alpha,bm_one);
And then in the layer_script_end() reseting the blendmode:
Code:
surface_copy(background_surface,0,0,application_surface);
gpu_set_blendmode(bm_normal);
The above blendmode uses the normal process for blending rgb values, but it ensures that the maximum (or higher) opacity is maintained. So drawing something 50% opaque on something 50% opaque gives you 75% opacity (like Photoshop would) and not 50% opacity like bm_normal does.

And even this is not perfect, as you are rendering onto black pixels (though fully transparent, the rgb information is still there) and not the pixels of the actual background. So trying to get additive blending to work on this layer will not work as you are adding to black pixels and not the actual background pixels.

Though for the purposes of using the layer as lighting, it should work perfectly as you are applying the additive blending when you are drawing the surface (and not drawing to the surface).
 
Last edited by a moderator:
I had this exact same problem recently. I wanted to render multiple layers to different surfaces so I could composite them later using masks and shaders. The solution I came up with was using the application_surface as a sort of temp surface.
I actually used this same method for a while at first, but after profiling, I decided that using surface copy every frame multiple times was not ideal.
It is a good idea though, and works perfectly. I'm not sure if surface copying is any faster in GM2... I reworked my draw pipeline to do what I said earlier - I manually offset the coordinates and scale of drawing.

Let me know how your profile goes.

Also, let me elaborate on my system. I now have objects that handle certain drawing layers, so that the in-engine depth variable works. I have my lighting object draw at depth -1000.

This lighting object loops through all lights, additively draws them to it's surface, then draws the surface on the render target with a blend mode. It's just as if it's any normal object. So I can have objects with even less depth like -1100 that actually draw IN FRONT of lights, like emissive objects or things that SEEM like GUI objects, but exist within the view space (enemy health bars, etc.)

This might not be something you need, and it's still possible on the "temporary holding surface" anyway (by using ANOTHER surface that draws foreground objects), but it's just a little thing that is actually easier with this method.
 
Top