Effects

In order to actually render something to the screen you need to make one or multiple effects. What these effects are doing is entirely up to you. Effects have methods for fetching loaded resources and existing effect instances. Effects can also create new instances of effects if needed. This would happend during initialization.

Effect examples can be found in the examples directory in the root of the repository.

A bascic effect would have the following structure:

from demosys.effects import Effect

class MyEffect(Effect):

    def __init__(self):
        # Do initialization here

    def draw(self, time, frametime, target):
        # Called every frame the effect is active

The Effect Package

The effect package should have the following structure (assuming our effect package is named “cube”).

cube
├── effects.py
├── dependencies.py
└── resources
    └── programs
        └── cube
            └── cube.glsl
    └── textures
    └── scenes
    └── data

The effects.py module can contain one or multiple effects. The effect package can also have no effects and all and only provide resources for other effects to use. The effects.py module is still required to be present.

Dependencies

The dependencies.py module is required to be present. It describes its own resources and what effect packages it may depend on.

Example:

from demosys.resources.meta import ProgramDescription

effect_packages =
    'full.python.path.to.another.package',
]

resources = [
    ProgramDescription(label='cube_plain', path='cube_plain.glsl'),
]

Resources are given labels and effects can fetch them by this label. When adding effect package dependencies we make the system aware of this package so their resources are also loaded. The effects in the depending package will also be registered in the system and can be instantiated.

Resources

The resources directory contains fixed directory names where resources of specific types are supposed to be located. When an effect package is loaded, paths to these directories are added so the system can find them.

Note

Notice that the resource directories contains another sub-directory with the same name as the effect package. This is because these folders are by default added to a project wide search path (for each resource type), so we should place it in a directory to reduce the chance of a name collisions.

Having resources in the effect package itself is entirely optional. Resources can be located anywhere you want as long as you tell the system where they can be found. This is covered in Settings.

Reasons to have resources in effect packages is to create an independent resuable effect package you could even distribute. Also when a project grows with lots of effect packages it can be nice to keep effect specific resources separate.

We currently support the following resource types:

  • Shader programs
  • Scene/mesh data (glfw 2.0 or wavefront obj)
  • Textures (loaded with Pillow)
  • Data (generic data loader supporting binary, text and json)

We load these resources by creating resource description instances:

from demosys.resources.meta import (TextureDescription,
                                    ProgramDescription,
                                    SceneDescription,
                                    DataDescription)

# Resource list in effect package or project
resources = [
    # Textures
    TextureDescription(label="bricks", path="bricks.png"),
    TextureDescription(label="wood", path="bricks.png", mipmap=True),

    # Shader programs
    ProgramDescription(label="cube_plain", path="cube_plain.glsl"),
    ProgramDescription(
        label="cube_textured",
        vertex_shader="cube_textured.vs",
        fragment_shader="cube_textured.fs"
    ),

    # Scenes / Meshes
    SceneDescription(label="cube", path="cube.obj"),
    SceneDescription(label="sponza", path="sponza.gltf"),
    SceneDescription(label="test", path="test.glb"),

    # Generic data
    DataDescription(label="config", path="config.json", loader="json"),
    DataDescription(label="rawdata", path="data.dat", loader="binary"),
    DataDescription(label="random_text", path="info.txt", loader="text"),
]

The Effect base class have methods avaiable for fetching loaded resources by their label. See the demosys.effects.Effect.

There are no requirements to use the resource system, but it provides a convenient way to ensure resources are only loaded once and are loaded and ready before effects starts running. If you prefer to open files manually in an effect initializer with open you are free to do that.

You can also load resources directly at an point in time by using the resources package:

from demosys.resources import programs, textures, scenes, data
from demosys.resources.meta import (TextureDescription,
                                    ProgramDescription,
                                    SceneDescription,
                                    DataDescription)

program = programs.load(ProgramDescription(label="cube_plain", path="cube_plain.glsl"))
texture = textures.load(TextureDescription(label="bricks", path="bricks.png"))
scene = scenes.load(SceneDescription(label="cube", path="cube.obj"))
config = data.load(DataDescription(label="config", path="config.json", loader="json"))

This is not recommended, but in certain instances it can be unavoidable. An example could be loading a piece of data that references other resources. These are common to use in resource loader classes. Also, if you for some reason need to load something while effects are already, this would be the solution.

Running an Effect Package

Effect packages can be run by using the runeffect command:

python manage.py runeffect <python.path.to.package>

This will currently look for the first effect class with the runnable attribute set to True, make an instance of that effect and call its draw method every frame. The effect package dependencies are also handled. (All handled by DefaultProject class)

The runnable effect is resposible for instantiating other effects it depends on and call them directly.

If you need a more complex setup where multiple runnable effects are involved, you need to create a proper project config using project.py and instead use the run command.