====== Initial TileSamplerZ implementation ====== /* Started on 08/02/2022 */ * 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: * 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 fb, int x, int y, int w, int h, TextureFormat f, PixelType t, ptr 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); } * => Actually found an interesting page the [https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices|WebGL Best practices], also suggesting to read pixels asynchronously. But I need to understand how this works now 🤣 * => 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 { 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.