Kode / Kinc

Modern low level game library and hardware abstraction.
http://kinc.tech
zlib License
517 stars 120 forks source link

Graphics4 API awkwardness #255

Open johngirvin opened 6 years ago

johngirvin commented 6 years ago

In developing a "flexible 2D renderer" - a rendering layer for 2D games that can cope with arbitrary geometry (sprites, text, polygons), shaders with various vertex structures, doing at least some draw call batching, and taking as much boilerplate work off the caller as possible - i've run in to some things in the Graphics4 API that make it difficult.

Observations only based on limited development of the renderer. I may just need to rethink my approach and assumptions!

eg: if the user instructs the renderer to set a texture, how does the renderer know if this is actually changing anything? TextureUnit and Texture instances can't be compared easily. I think.

These are the ones I've run in to so far. There may be other state objects to consider.

RobDangerous commented 6 years ago

Can sure help out with the comparisons but have to have a closer look at the various graphics APIs for the vertex structure changes... I think it should work but not sure yet...

RobDangerous commented 6 years ago

I added some ids.

johngirvin commented 6 years ago

Thanks :) Some comments:

  1. Should Texture and TextureUnit ids just reflect the back end unit and id? The aim is to track which texture is bound to which unit, not which TextureUnit instance was bound to which Texture instance. They can amount to the same if the user is careful with instances, but...

  2. Similar, but for PipelineState and back end program id.

  3. RE: VertexBuffers. Allowing VertexBuffers with different structures to share the same underlying float[] data buffer would suffice, I think? eg: assign or reassign the 'data' member of the OpenGL back end. It would need to track if it was using a user-supplied buffer to avoid deleting it if the VertexBuffer instance was destructed.

Is this perhaps not how G4 was intended to be used?

RobDangerous commented 6 years ago

Sorry, I'm completely confused. Can you maybe provide some sample code that shows what you're trying to do?

johngirvin commented 6 years ago

For (1) - I need to track which Texture is bound to which back end hardware texture unit so I can avoid repeatedly binding the same texture.

The natural way to do that is to keep a map of texture unit id -> texture id, however If Kore::TextureUnit.id is just an incrementing integer as in the current implementation, there's no correlation between that value and the hardware texture unit id. This causes problems.

Consider this (WIP) method from the draw call batching class I'm developing:

    void Batcher::set(TextureUnit newUnit, Texture *newTex, TextureFilter newTexFilter, TextureAddressing newTexAddressing)
    {
        auto &result = state.texture[newUnit.id];
        if (result.textureId == newTex->id && result.filter == newTexFilter && result.addressing == newTexAddressing) { return; }

        // flush current batch before state change
        flush();
        lock();

        // update state
        if (result.textureId != newTex->id)
        {
            Kore::Graphics4::setTexture(newUnit, newTex);
        }
        if (result.textureId != newTex->id || result.filter != newTexFilter)
        {
            Kore::Graphics4::setTextureMinificationFilter(newUnit , newTexFilter);
            Kore::Graphics4::setTextureMagnificationFilter(newUnit, newTexFilter);
        }
        if (result.textureId != newTex->id || result.addressing != newTexAddressing)
        {
            Kore::Graphics4::setTextureAddressing(newUnit, Kore::Graphics4::U, newTexAddressing);
            Kore::Graphics4::setTextureAddressing(newUnit, Kore::Graphics4::V, newTexAddressing);
        }
        Kore::Graphics4::setTextureMipmapFilter(newUnit, Kore::Graphics4::NoMipFilter);

        result.textureId  = newTex->id;
        result.filter     = newTexFilter;
        result.addressing = newTexAddressing;
    }

If I call this in a sequence such as:

texUnit1 = pipeline1->pipeline.getTextureUnit("u_diffuse"); // texUnit1.id = 1 ; texUnit1.unit = 0
texUnit2 = pipeline2->pipeline.getTextureUnit("u_diffuse"); // texUnit2.id = 2 ; texUnit2.unit = 0
...
batcher->set(texUnit1, tex1, ...);
[drawing1]
batcher->set(texUnit2, tex2, ...);
[drawing2]
batcher->set(texUnit1, tex1, ...);
[drawing3]

...drawing3 will still have tex2 bound to texture unit 0, because the batcher state map entry for 1 will be tex1 and therefore the third set() call will detect no need to rebind tex1.