============
Text Shaders
============

Ren'Py contains a text shader system that makes it possible to control how
Ren'Py displays text. When enabled, the text shader system uses :doc:`model`
to render two triangles for each unicode character. Shader parts, either specified
from the creator or generated by Ren'Py, are applied to the model, and
can change how text is displayed.

The text shader documentation is in three parts:

#. How to use text shaders
#. What text shaders are included in Ren'Py
#. How to create new text shaders

Note that while text shaders are intended to be easily used by game creators,
making your own text shaders requires some knowledge of GLSL, the OpenGL Shading
Language, as well as :doc:`model`, and hence is more advanced than most Ren'Py
features.


Using Text Shaders
===================

There are three ways to use text shaders:

**Default Text Shader** The first is to set the default
text shader, using :var:`config.default_textshader`. ::

    define config.default_textshader = "wave:10"

When set this way, the text shader will be used for all text that does not
specify a text shader. It will also be combined with text shaders that
include the default text shader, which is most of them.

Generally, the default textshader should take care of slow text and
shouldn't add more complicated effects.

**Styles** The second way to use text shaders is to set the :propref:`textshader`
style property, either directly or in one of the many ways provided by Ren'Py to
set styles. ::

    style default:
        textshader "dissolve"

    define goldfinger = Character("Goldfinger", what_textshader="linetexture:gold.png")

    screen purchase():
        vbox:
            text "Buy the DLC for more!" textshader "wave"
            textbutton "Buy Now":
                text_textshader "wave" action iap.Purchase("dlc")

**Text Tags** The third way to use text shaders is to use the :tt:`appropriate text tag <shader>`
to change the look of a portion of text. ::

    "What's this? A letter from {shader=zoom}out of nowhere{/shader}?"

**Note** A text block should either use text shaders or not - mixing is not
supported. For example, you should set :var:`config.default_textshader`
or :propref:`textshader` style property if you use the text tag like above.

Specifying Text Shaders
-----------------------

Text shaders are specified as strings like::

    "dissolve"
    "jitter:u__jitter=1.0, 3.0"
    "texture:gold.png"

The first part of the string, before the first colon, is the name of the text shader.
The rest of the string is a series of uniforms that are passed to the shader,
separated by colons. (Uniforms are parameters that are passed to the shader,
that can be used to control how the shader works.)

Uniforms can be specified by name followed by =, or the name can be
omitted to set each uniform in order. (Omitting the name is not supported in Ren'Py 7.) While
internally all uniforms begin with u\_, the u\_ can be omitted for brevity.

The value of a uniform can be:

* Between 1 and 4 numbers, separated by commas. These can be used with the
  the float, vec2, vec3, or vec4 types.
* A color, beginning with #. (For example, #f00 or #ff0000 for red.) This
  creates a vec4 corresponding to that color. This color will be
  premultiplied by its alpha channel.
* A :doc:`displayable <displayables>` that will be
  used as a texture. This creates a sampler2D that can be used to sample
  the texture.

Uniform values can't be expressions or access variables, though it is
possible to use text interpolation to create a string that can be
evaluated as a textshader tag or its parameter.


Finally, text shaders can be combined with each other using the | operator.
For example::

    "jitter:1.0, 3.0|wave"

This will apply both the jitter and wave shaders to the text. This only works
if the shaders are compatible with each other, and do not use the same
uniforms (or use the uniform in a way that is compatible with each other, in
which case it takes the value from the last shader in the list).

Unless a textshader has `include_default` set to False, the default textshader
will be combined with the textshader specified in the style or tag.


Text Shader Callbacks
---------------------

The :var:`config.textshader_callbacks` variable can be used to set a callback that
is run when a text shader is applied. This can be used to customize the text
shader based on a preference. ::


    default persistent.dissolve_text = True

    init python:
        def get_default_textshader():
            if persistent.dissolve_text:
                return "dissolve"
            else:
                return "typewriter"

    define config.default_textshader = "default"
    define config.textshader_callbacks["default"] = get_default_textshader

Built-In Text Shaders
=====================

Ren'Py includes a number of built-in text shaders. These are:

.. include:: inc/builtintextshaders


Creating Text Shaders
=====================

Text shaders are GLSL programs that are run on the GPU. These shaders are
registered using the renpy.register_text_shader function.

.. include:: inc/textshader

Variables in Text Shaders
-------------------------

In additions to the uniforms you provided to the text shader (generally beginning with ``u__``),
Ren'Py makes the following variables available to text shaders. To use a variable in a text shader,
it needs to be declared in the `variables` argument to renpy.register_text_shader.

In addition to these, the model :ref:`uniforms and attributes <model-uniforms>` are available, with
`a_position`, `a_tex_coord`, `u_time` and `u_random` being particularly useful.


Uniforms
^^^^^^^^

``float u_text_depth``
    The depth of the text from the top. The topmost text has a depth of 0.0, the first outline or shadow has a
    depth of 1.0, the second outline or shadow has a depth of 2.0, and so on.

``float u_text_main``
    If this is 1.0, the text is the main text. If this is 0.0, the text is the outline or shadow of the main text.

``float u_text_max_depth``
    The maximum value of u_text_depth. This is the number of outlines and shadows that will be drawn. When u_text_depth
    is equal to this value, the texct is the last outline or shadow, which may be useful for drawing backgrounds.

``vec2 u_text_offset``
    The offset of the text from the center of the character. This is in drawable pixels in x, y order.

``float u_text_outline``
    The width of the outline around the text. This is in drawable pixels, and is the distance from the edge of the text to the edge of the outline.

``float u_text_slow_duration``
    The duration of a single slow character, when showing slow text. 0.0 if not showing slow text.

``float u_text_slow_time``
    The time in seconds since the start of the slow text effect. This will only increase until the end of slow
    text, when it will max out. If the user clicks to terminate slow text, this will max out. It should only
    be used for slow text - use

``float u_text_to_drawable``
    The ratio of virtual pixels to drawable pixels. This is used to convert from virtual pixels to drawable pixels.

``float u_text_to_virtual``
    The ratio of drawable pixels to virtual pixels. This is used to convert from drawable pixels to virtual pixels.

``sampler2D tex0``
    This texture contains the rendered text at the current depth.

``vec2 res0``
    The resolution of tex0, in drawable pixels.


Attributes
^^^^^^^^^^

When drawing text, each vertex corresponds to a single glyph. Multiple glyphs may have vertices that share
locations, but these are passed to the shader as different vertices.

``float a_text_ascent``
    The ascent of the current glyph's font above the baseline, in drawable pixels.

``vec2 a_text_center``
    The position of the center of the glyphs's baseline, in drawable pixels. This is not the
    center of the rectangle, it's a point on the baseline and around the center of the character.

``float a_text_descent``
    The descent of the current glyph below the baseline, in drawable pixels.

``float a_text_index``
    The index of the glyph being drawn. This is 0 for the first vertex and goes up by one for each vertex.

``vec2 a_text_min_time``
    The minimum time at which any vertex of the glyph should be shown. When showing from left-to-right,
    this is the time the leftmost vertices should be shown. When the text is meant to be shown instantly
    but ``u_text_slow_duration`` is not 0.0, this will be -3600.0.

``vec2 a_text_max_time``
    The maximum time at which any vertex of the glyph should be shown. When showing from left-to-right,
    this is the time the rightmost vertices should be shown. When the text is meant to be shown instantly
    but ``u_text_slow_duration`` is not 0.0, this will be -3600.0.

``float a_text_time``
    The time at which this vertex should be shown. When the text is meant to be shown instantly
    but ``u_text_slow_duration`` is not 0.0, this will be -3600.0.

``vec4 a_text_pos_rect``
    The rectangle containing the glyph, in drawable pixels. This is a vec4 with the x, y, width, and height of the rectangle,
    in drawable pixels. This can be converted to texture coordinates by dividing it by ``res0``.

Pseudo-Glyphs
-------------

Ren'Py will creates pseudo-glyphs that cover the start and end of each line. If a line
is blank, one pseudo-glyph is created covering the whole line. These pseudo-glyphs are necessary
to cover areas where outlines may extend into a line above or below the current line.

Example
-------

This is an example of a text shader that spins text when shown. ::

    init python:

        def adjust_extra_slow_time(ts, u__delay, **kwargs):
            """
            Adjusts a text shader's extra slow time to support the spinning text shader.
            """
            ts.extra_slow_time = u__delay

        renpy.register_textshader(
            "spin",
            adjust_function = adjust_extra_slow_time,

            variables = """
            uniform float u__delay;
            uniform float u__offset;
            uniform float u_text_slow_time;
            attribute vec2 a_text_center;
            attribute float a_text_min_time;
            """,

            vertex_50 = """
            float l__angle = clamp((u_text_slow_time - a_text_min_time) / u__delay, 0.0, 1.0) * 2.0 * 3.1415926536;
            float l__sin = sin(l__angle);
            float l__cos = cos(l__angle);

            gl_Position.y -= u__offset;
            gl_Position.xy -= a_text_center;
            gl_Position = vec4(
                gl_Position.x * l__cos - gl_Position.y * l__sin,
                gl_Position.x * l__sin + gl_Position.y * l__cos,
                gl_Position.z,
                gl_Position.w
                );
            gl_Position.xy += a_text_center;
            gl_Position.y += u__offset;
            """,

            u__delay = 1.0,
            u__offset = 0,
        )


It can be used witt the following script::

    define config.default_textshader = "typewriter"

    label start:

        "This is a test of the {shader=spin:0.5:-5}spin{/shader} text shader."
