public:projects:nervland:notes:0004_landjs_initial_tilesamplerz

Initial TileSamplerZ implementation

  • Hey hey hey! So we continue our investigations on the ElevationProducer usage today. And so, the first thing I can note is that in the reference terrain1/helloworld.xml file from Proland, we are assigning our ElevationProducer to the TerrainNode somehow:
        <node name="scene">
            <node flags="camera">
                <method id="draw" value="cameraMethod"/>
            </node>
    
            <node name="terrainNode" flags="object,dynamic">
                <bounds xmin="-50000" xmax="50000" ymin="-50000" ymax="50000" zmin="0" zmax="5000"/>
                <field id="terrain" value="terrain"/>
                <tileSamplerZ id="elevation" sampler="elevationSampler" producer="groundElevations1" storeInvisible="false"/>
                <tileSampler id="fnormal" sampler="fragmentNormalSampler" producer="groundNormals1" storeParent="false" storeInvisible="false"/>
                <mesh id="grid" value="quad.mesh"/>
                <method id="update" value="updateTerrainMethod"/>
                <method id="draw" value="drawTerrainMethod"/>
                <module id="material" value="terrainShader"/>
            </node>
        </node>
  • Let's first check what details we can get on that tileSampleZ attribute: the TileSampleZ class is a derived class from TileSampler.
  • Currently we only have a minimal skeleton implementation for TileSampler in nvland, so let's add both classes now.
  • ⇒ Added correct implementation for TileSampler constructor, and some of the getters.
In the TileSample constructor documentation it is reported that the producer parameter may be null in some cases, but that is incorrect: we always try to access the producer data in the constructor so it must be valid.
  • Argghh… 😖 We are starting to get a pretty large mess of classes, and inner classes, and inheritance, and all… I hope I can manage that till the end :-S (Currently implementing “TileSamplerZTree” class)
  • Oh my god… Now we also have the TileSamplerZ::State class and yet another factory for that lol. Maybe I should stop here for tonight 😅.
  • Okay, now back to this task with at least some energy to put in it: let's continue on the TileSamplerZState implementation.
  • ⇒ we need to add the method FrameBuffer::setTextureBuffer(): Arrf, no, in fact we already have that as setTexture2DBuffer() 😁
  • We need to implement GPUTileStorage.getTexture(): OK
  • Okay, and next, probably another big chunk: we need the ReadbackManager implementation, let's see…
  • Hhmmmm, this is all interesting: this ReadbackManager class is all about reading back some data from the GPU to the CPU “asynchronously”
  • Except that, I'm not completely sure this is really asyn yet ? (well it should I guess), it's mostly happening in:
    bool ReadbackManager::readback(ptr<FrameBuffer> fb, int x, int y, int w, int h, TextureFormat f, PixelType t, ptr<Callback> cb)
    {
        if (readCount[0] < maxReadbackPerFrame) {
            int index = readCount[0];
            fb->readPixels(x, y, w, h, f, t, Buffer::Parameters(), *(toRead[0][index]));
            toReadCallbacks[0][index] = cb;
            ++readCount[0];
            return true;
        } else {
            assert(false); // should not happen, call canReadback before
            return false;
        }
    }
  • Checking the FrameBuffer::readPixels(), this is based on glReadPixels:
    void FrameBuffer::readPixels(int x, int y, int w, int h, TextureFormat f, PixelType t, const Buffer::Parameters &s, const Buffer &dstBuf, bool clamp)
    {
        if (Logger::DEBUG_LOGGER != NULL) {
            Logger::DEBUG_LOGGER->logf("RENDER", "read %d pixels", w * h);
        }
        set();
        dstBuf.bind(GL_PIXEL_PACK_BUFFER);
        s.set();
        glClampColor(GL_CLAMP_READ_COLOR, clamp ? GL_TRUE : GL_FALSE);
        glReadPixels(x, y, w, h, getTextureFormat(f), getPixelType(t), dstBuf.data(0));
        s.unset();
        dstBuf.unbind(GL_PIXEL_PACK_BUFFER);
        dstBuf.dirty();
        assert(getError() == 0);
    }
  • ⇒ Okay, so yes: maybe we could implement the ReadbackManager.readback() method so that it returns a promise. And then we can call the callback we want after that promise ?
  • So, this bring the question of: do we need the ReadbackManager at all ? is we simply just add the readPixelsAsync to the Framebuffer, then we could pass callback there, so no need for readbackManager and/or call to nextFrame() (?). Let's try that path.
  • We now have our constructor for the State class, next, we need the factory for it: OK, we now have the class TileSamplerZStateFactory, we need to “install that” now: this should be done in the init for TileSamplerZ:
        async init() {
            // Get or create the state factory:
            let factory = RenderContext.getCurrent().getOrCreateFactory('TileSamplerZState', TileSamplerZStateFactory);
    
            // Create our state object:
            let storage = this.producer.getCache().getStorage().asGPUTileStorage();
            this.state = await factory.get(storage);
        }
  • And finally, i'm adding an helper function to create a TileSamplerZ object async:
    export async function createTileSamplerZ(producer: TileProducer, name: string = null): Promise<TileSamplerZ> {
        let ts = new TileSamplerZ(producer, name);
        await ts.init();
        return ts;
    }
  • Cool! ⇒ Now I should be able to instanciate a TileSamplerZ object 😳.. Well, in theory… Let's try that 😅.
  • Hmmm, and now I'm just realizing that the name parameter for a TileSampler should really be the GLSL uniform name for the corresponding sampler ⇒ using the default “TileSampler” name doesn't make any sense then.
  • Okay so… not quite working out of the box unfortunately lol… I seem to get a freeze in the process when starting to create the TileSamplerZState. Let's see… puuff, stupid me: we get an infinite loop with that kind of while if not using real integers of course:
            let pass = 0;
            while (h != 1) {
                h = h / 4 + (h % 4 == 0 ? 0 : 1);
                pass += 1;
            }
  • ⇒ Yeepee! And now it seems I'm creating my TileSampleZ object without any further trouble! Good good.
  • Some final touch now on the init process… ⇒ just calling tsZ.setStoreInvisible(false) and now we are good!

⇒ Next time, we will create the TileSampler class… or maybe not 🤔: we probably need to start with the NormalProducer first.

  • public/projects/nervland/notes/0004_landjs_initial_tilesamplerz.txt
  • Last modified: 2022/02/09 20:28
  • by 127.0.0.1