Proposal: Tilemap changes

I’m running into the same issue, currently on openfl 4.4.1, lime 3.4.1. Setting tile.visible to false seems to have no effect on whether or not it renders.

Is this still a known issue?

EDIT: Looks like I posted too soon–after a bit more debugging it looks like there was an issue in my code. tile.visible is working for me.

1 Like

This might be weird, but you may also be able to do openfl test html5 but connect to the running HTTP server from your desktop. I do this sometimes by running on the desktop, but opening in a mobile browser :slight_smile:

Agree. the possibility to add different filters to each Tile would be a real plus. Especially if it allows to make a single GPU draw call for all Tiles with same Bitmapdata but different filters applied to them (to model particles effects for example. It should make a huge difference in performance with the “naive” method consisting in having one sprite/particle)

Assuming filters are implemented at the shader level, then different filters require separate GPU calls. However, the same type of filter with different parameters might be fine. (For instance, if all particles used glow filters, but they had different glow colors, that might be possible with a single GPU call.)

yes I was thinking mainly of same filters for each Tile but with parameters that may change for each (like the matrix for a ColorMatrixFilter or the color of a GlowFilter)
Anyway, why would different filters (shaders) require multiple GPU calls. Can’t you send to openGL a batch of draw commands with bitmapdata and associated shaders in a single “call”?

A GPU is a whole bunch of small processors, letting it perform lots of simple calculations in parallel. It’s called a Graphics Processing Unit because graphics generally require a large number of simple calculations, and the calculations can be done in parallel.

Shaders are the programs that the GPU runs. When you’re going to use a shader, you upload it to the GPU, the GPU distributes it to all of the parallel processors, and the processors prepare to run it. Then you pass some data to the GPU, the GPU splits up the data among its processors, and the processors run the program on the data.

There’s more data than there are processors, so each processor is going to need to run the shader multiple times. But since they never switch shaders, there’s no delay. GPUs don’t even offer the option to switch shaders midway, because that would require a lot of extra if statements, which would slow everything down whether or not any switching happened.

1 Like

What if we use a shader for all tiles but we pass some attribute per Tiles (instead of an uniform which need a shader per Tile so it means one draw call per filter), one of these attributes could activate or not the shader for the current vertex.
Example:
-Tile 1 (use shader)
attribute activate bool true
attribute colorFilter
-Tile 2 (don’t want to use the shader)
attribute activate bool false
attribute colorFilter

Then we pass the attributes in the vertex shader, convert it to varying to make it available in fragment shader.
And in the fragment shader, if the varying activate is true, we manipulate the color. But if it is false, we output the original color. It should use one draw call right?

1 Like

You’d have to write the shader yourself, but yes, that would definitely work.

Note: if statements are relatively slow on GPUs. As counterintuitive as it sounds, it’s better to perform all of the steps every time, whether or not you’re going to use the result. To control whether you use the result, multiply by the boolean attribute. (A boolean is either 1 or 0, so it either leaves the result unchanged or removes it entirely.)

1 Like

OK. So basically the best solution would be to only have Shaders at the Tilemap level and to pass attributes for each Shader at the Tile level (with a storing of type Map<Shader ID, Shader Attributes Object> stored for each tile). If a Tile has no Shader attribute for a specific Shader (so the map returned a null object), then the shader would be runned with default parameters but the result would be discard at the end of the shader program. Else the shader is runned with parameters passed to it from the Tile.
And If you have two types of tiles (1 with bitmapdata1 and another with bitmapdata2) and two shaders: shader1 and shader2, the most efficient way to run the process is to do
GPU call with shader1 and bitmapdata1
GPU call with shader1 and bitmapdata2
GPU call with shader2 and bitmapdata1
GPU call with shader2 and bitmapdata2

Sounds right?

Yes, and we need a way to define properties which should be bound for each tile, for example:

tilemap.shaderData.set ("velocity", "uVelocity");

// internally

shader.data.uVelocity.value = tile.data.velocity;

I don’t know of a good API for this (at the moment), but in principle, we would need a way to specify additional attributes to be bound for the shader

Why can’t shader properties bound to each tile be a simple Dynamic object (stored in a shadersdata map with a shaderID key identifier at the tile level) and do something like:

shader.data = (tile.shadersdata.get(shaderID)==null)?defaultshaderdata:tile.shadersdata.get(shaderID);

The more I look at these last two posts, the less I understand them. Are you talking about uniform variables or tile-specific attributes?

A uniform variable is the same for every single vertex. The same value will be applied to all four vertices in tile 0, and all four vertices in tile 1, and so on.

One example of a uniform variable is the view matrix (basically, the camera). Every vertex is part of the same view, so every vertex gets the same matrix. Passing a separate copy of the matrix to every single vertex would be a total waste.

An attribute is a variable that only applies to one vertex. For instance, x, y, and z have to be attributes, since you need distinct points to draw a shape.

Another example is velocity. If velocity was a uniform variable, then everything would move in the same direction at the same speed. (And if that’s all you want to do, you might as well just update the camera.) Usually, if you go to the trouble of defining a velocity variable, it’s because you want one object to move relative to other objects.

You can’t (easily) store the new position after moving an object, but velocity still has a valid use in a shader program. Specifically, it’s useful if you know a particle’s trajectory in advance. In a particle system, for instance, you might spawn particles, give them a velocity, and let them go forever.

In this particle system, you can calculate a particle’s exact position based on only three things: (1) its starting location, (2) its velocity, and (3) the time elapsed. (1) and (2) need to be attributes, but (3) could be uniform, since time passes at the same rate for all particles. Since a particle’s starting location and velocity never change, this means you only need to update one value every frame: the time elapsed.

Anyway, that’s just my attempt at explaining why you’d want a velocity attribute. Sorry if it was a bit hard to follow, but the details weren’t my main point.

My main point is, if you want to define velocity data, you’re going to want a separate velocity value for every single tile. And that’s why Joshua’s post confused me.

This appears to set a velocity property for the entire Tilemap. But… that would make it a uniform value.

This implies that each Tile object has its own velocity data, which is what I’d expect. However, this data is then assigned to the Shader object, again making it a uniform property.

There’s no way this structure can store a separate velocity value for every tile.

shader.data.uVelocity.value = tile0.data.velocity;
shader.data.uVelocity.value = tile1.data.velocity;
shader.data.uVelocity.value = tile2.data.velocity;

You’ll just overwrite it each time, and every tile will end up with the final tile’s velocity. Either that or you run a draw call for every tile, which defeats the point of Tilemap.

If all you’re looking for is syntactic sugar for attributes, I might use subclasses:

class MyParticleTile extends Tile {
    @:attribute(x, y, z) public var velocity:Vector3D;
    
    public function new(velocity:Vector3D) {
        super(2);
        this.velocity = velocity;
    }
}

Then a build macro would add a function to copy the attributes into a buffer, as well as a static variable indicating how many indices are used:

public static var __bufferSpaceRequired:Int = 3;

public override function __copyAttributes (bufferData:Float32Array, offset:Int):Void {
    
    bufferData[offset + 0] = velocity.x;
    bufferData[offset + 1] = velocity.y;
    bufferData[offset + 2] = velocity.z;
    // velocity.w isn't included because the @:attribute annotation didn't mention it.
    
}

// Optional: also add a function to check shader.data to make sure it matches.

This means you wouldn’t be able to assume that the length is count * 30 anymore, but then again, that’s true no matter how you do this. Instead, you’d use count * (tileClass.__bufferSpaceRequired + 30), or else calculate it based on shader.data (which will match if the shader source code is correct).

After that, you’d need to make sure that all tiles define this attribute, because the shader is going to assume that everything does. I guess you could set default values like Thomas suggested, but that strikes me as risky. Instead, I’d suggest something more like this:

class TypedTilemap<T:Tile> #if !flash DisplayObject #else Bitmap implements IDisplayObject #end {
    
    //...
    
    private var __tiles:Array<T>;
    private var __tileClass:Class<T>;
    
    public function new (tileClass:Class<T>, width:Int, height:Int, tileset:Tileset = null, smoothing:Bool = true) {
        
        //...
        
        __tileClass = tileClass;
        
        //...
        
    }
    
    public function addTile (tile:T):T {
        
        __tiles.push (tile);
        __dirty = true;
        numTiles++;
        
        return tile;
        
    }
    
    //...
    
    
}

How do you keep this from messing up existing code? Simple:

class Tilemap extends TypedTilemap<Tile> {
    
    
    public function new (width:Int, height:Int, tileset:Tileset = null, smoothing:Bool = true) {
        
        super (Tile, width, height, tileset, smoothing);
        
    }
    
    
}

For added usability, the build macro could create the constructor automatically, so users would be able to skip it if they wanted. Then defining a custom subclass would be this easy:

class MyTile extends Tile {
    @:attribute(x, y, z) tintColor:Vector3D;
    @:attribute tintStrength:Float = 0;
}

Instantiating the Tilemap would be just a bit different, but everything after that is intuitive and type-safe:

tilemap = new TypedTilemap(MyTile, stage.stageWidth, stage.stageHeight, tileset);

tilemap.addTile(new MyTile(new Vector3D(0.5, 1, 1)));
tilemap.getTileAt(0).tintStrength = 0.75;
2 Likes

A special subclass may be a good idea. My proposal was what to define a mapping between an attribute name in the shader, and a reflect variable in each tile.

The idea is that each tile.data would have velocity, and be bound to aVelocity in the shader

You could base everything on the shader source. So if the shader source declared attribute vec3 velocity, you’d read values from tile.data.velocity.

However, this doesn’t guarantee that every tile defines the right data, so you’ll have to use default values. Either force the user to set a default value, or use default values of 0.

Question: does OpenFL currently use Tile.data for anything? If not, I’d like to suggest switching to a type-safe solution.

In this case, the usual benefit of reflection (being able to put whatever you want in the object) is actually a drawback. Since everything has to be compatible with the shader, each tile has to have a very specific set of variables. Extra variables will be ignored, and missing variables will need to be added. This makes reflection just as restrictive as my implementation, but without the code completion and compile-time checks.

(And for the record, I’m not saying my implementation is perfect. I’ll bet it could be streamlined a bit more.)

1 Like

tile.data is there for undefined user data, you can extend the Tile class to accomplish this, but that would be more work than (potentially) a quick tile.data.exploded = true or whatever someone needs.

Defined based on the custom shader would be smart, better than having to do a clumsy Map like I suggested :slight_smile:

I guess that’s a valid use case, but I have a few nits to pick.

First, there’s still the issue of type-safety. Yes it’s easier to type tile.data.exploded = true, but then if you access tile.data.exploed, it’ll let you. You don’t even get a runtime error - accessing that value returns null, which is coerced to false.

Second, it doesn’t match existing classes. If you’re working with Sprite or Bitmap objects, you have to make a subclass to add your own variables. Does this need to be different?

Third, Tile's constructor doesn’t set data = {}. This is a good thing for memory usage, but it means you have to create it yourself if you want to use it. Depending on how you structure the code, this could be as simple as typing tile.data = {} once, or as complicated as typing if(tile.data == null) tile.data = {} many times. In most cases it’ll still be faster than defining a subclass, but not as much faster.


Another possibility is to move this functionality into a subclass:

class DynamicTile extends Tile implements Dynamic {
    
    public function new (id:Int = 0, x:Float = 0, y:Float = 0, scaleX:Float = 1, scaleY:Float = 1, rotation:Float = 0) {
        
        super (id, x, y, scaleX, scaleY, rotation);
        
    }
    
}

Question: does OpenFL currently use Tile.data for anything? If not, I’d like to suggest switching to a type-safe solution.

++

If you want to use undefined data you can make MyTile with undefined .data)) But that .data in base-class - it’s really pain in the ass for me when I inherit Tile class with my own data))

Oh please use Vector class! Because Vector is faster than Array class. Am i right? Check my post Optimize performance !

For example tiles

var tiles: Vector<Tile>;
.....

If you don’t like than your decision isn’t my own. Of course if you remember different performance of Vector and Array

Thanks for reading!

I’m late to the party, but pivot/anchor point would be great - currently I can’t implement rotation around center myself.