GML Apply surface only on part of screen

padth4i

Member
Recently I came upon this CRT filter and wanted to use it in my project. Essentially how it works is through one object, which draws the filter in the Draw GUI event. Here four passes are done to run each component of the filter (downscaling, scanline filter, screen distorting and post-processing). Shaders are used to draw all the components except the first one. Each shader is then drawn onto a temporary surface which is displayed.
Right now the code works fine when applying the filter over the whole screen. My goal is to apply this filter over only a 640 x 400 rectangle on the top right of the window. Is it possible to do this with surfaces or am I misunderstanding something completely?
 
One might assume a CRT effect would naturally only be applied to the whole screen (if you treat it as one "view" = one tv or monitor "screen") so my guess is the shader just uses the application surface for convenience. It captures the whole image, and fills the whole image, by default unless you really take control on the graphics side. CRT effects are most likely "post-processing", since it wants to happen after everything has been drawn to the screen (bar UI elements, which wouldn't want the effect, so are drawn last to be on "top" of it).

It's likely to use the application surface, as it is the same size as the view, and everything gets drawn to this surface at various stages throughout the graphics process. With the draw process having defined steps, this is admittedly an assumption that appears to fit quite neatly.

However: It seems you might want to have a view within a view, a "five nights at freddies" kind of set-up where you see a screen in each corner, and some have CRT effects whilst others are clearer pictures, or different timing for distortions etc.

I think it would be something like this:

1) create a surface that's 600 x 400 in size
2) draw to that, the area you want to show filtered
3) send that surface to the shader
4) draw the results of the shader to the application surface, or the view, at the position you want
5) repeat for however many "screens" you want to show: if you intend for each to have a different effect, then each has to be sent to the shader with altered parameters (level of noise for distortions, graininess, lighting, saturation, whatever)

It's not that much different from what you have - you just have to figure out changing the source, and content, of what is being passed to the shader.

If it does indeed use the application surface, then the drawing aspects (points 1 & 2) will be missing, since it most likely happens at a draw stage when GMS has already automatically processed the various visuals into a combined image (the application surface, post processing step)

No code will exist for creating this surface (that's always handled by the engine), and it's also possible that there's no code involved for drawing to it (the default setting is it being handled by the engine, but it can be handled by the user) as you'd simply send it to the shader during the correct draw step.

Point 4 would also be redundant, since
(a) the application surface covers the whole screen, as does the intended effect in the original example
(b) the surface being sent to the shader is...the application surface. Once it's returned with the shader transforms, it's automatically drawn afterwards.

Hopefully that helps you figure it out :)
 
Last edited:
If you look at lines 166 to 173 of oBJORTFX.object.gmx, you will see that it mentions the application surface.

(EDIT: My apologies. I just realised that this includes a load of engine code, as I just copied it straight from the object text. This will probably be in the create event of oBJORTFX)

GML:
// Clear
draw_clear_alpha(0, 0);

// Scale & draw the application surface
// (application surface can be substituted with any surface that contains the image that you want to apply the filter obviously)
draw_surface_stretched(application_surface, 0, 0, surfaceDownscaledWid, surfaceDownscaledHei);

surface_reset_target();
draw_surface_stretched(application_surface, 0, 0, surfaceDownscaledWid, surfaceDownscaledHei);

^^^ This is the only code, when using the application surface, as the rest is all done automatically. And it is merely shrinking it to fit the width / height of another surface. Then that surface is treated, treated again, and then a third time. After that it's redrawn to the application surface.


For your purposes: You want 3 surfaces (per "view", if you are doing multiple screens) that are 600 x 400 in size.

lines 63 to 123 cover this (sorry - you'll have to find where in the code this is, as the line numbering concerns the objects gmx file. This has engine code in it, so the numbering will differ)
Code:
/// Surfaces
// screen resolution
surfaceScreenWid = window_get_width();
surfaceScreenHei = window_get_height();
// downscaled screen resolution
surfaceDownscaleFactor = 1;
surfaceDownscaledWid = surfaceScreenWid >> surfaceDownscaleFactor;
surfaceDownscaledHei = surfaceScreenHei >> surfaceDownscaleFactor;
surfacePPDownscaled = surface_create(surfaceDownscaledWid, surfaceDownscaledHei); // surface holding the downscaled screen
surfacePPTemp1 = surface_create(surfaceScreenHei, surfaceScreenHei); // temp. surface to hold the result of pass 1
surfacePPTemp2 = surface_create(surfaceScreenWid, surfaceScreenHei); // temp. surface to hold the result of pass 2
application_surface_draw_enable(false);
</string>
          </argument>
        </arguments>
      </action>
    </event>
    <event eventtype="3" enumb="2">
      <action>
        <libid>1</libid>
        <id>603</id>
        <kind>7</kind>
        <userelative>0</userelative>
        <isquestion>0</isquestion>
        <useapplyto>-1</useapplyto>
        <exetype>2</exetype>
        <functionname></functionname>
        <codestring></codestring>
        <whoName>self</whoName>
        <relative>0</relative>
        <isnot>0</isnot>
        <arguments>
          <argument>
            <kind>1</kind>
            <string>/// Update surfaces
if (!surface_exists(surfacePPDownscaled) ||
    surface_get_width(surfacePPDownscaled) != surfaceDownscaledWid ||
    surface_get_height(surfacePPDownscaled) != surfaceDownscaledHei)
{
    if (surface_exists(surfacePPDownscaled))
        surface_free(surfacePPDownscaled);
    surfacePPDownscaled = surface_create(surfaceDownscaledWid, surfaceDownscaledHei);
}

if (!surface_exists(surfacePPTemp1) ||
    surface_get_width(surfacePPTemp1) != surfaceScreenWid ||
    surface_get_height(surfacePPTemp1) != surfaceScreenWid)
{
    if (surface_exists(surfacePPTemp1))
        surface_free(surfacePPTemp1);
    surfacePPTemp1 = surface_create(surfaceScreenWid, surfaceScreenWid);
}

if (!surface_exists(surfacePPTemp2) ||
    surface_get_width(surfacePPTemp2) != surfaceScreenWid ||
    surface_get_height(surfacePPTemp2) != surfaceScreenHei)
{
    if (surface_exists(surfacePPTemp2))
        surface_free(surfacePPTemp2);
    surfacePPTemp2 = surface_create(surfaceScreenWid, surfaceScreenHei);
}
surfaceScreenWid
surfaceScreenHei
surfaceDownscaledWid
surfaceDownscaledHei

are the variables to pay attention to regarding the size of the surfaces (I haven't fully parsed this project, so am just pointing out what I've noticed so far)

Swap out the application_surface in line 171 for a new surface that has whatever you want to show drawn to it, and that should (pretty much) be it.
 
Last edited:
Top