Hello Manu & Co. How are you today guys ?! On my side, well, it's a bit cold, but not that much, and it's the week-end yeepeee! Let's try to enjoy that, and write some cool code!
The proland::ResidualProducer class is a producer that can load in CPU memory precomputed residual tiles stored on disk. The residual tiles must be stored in files containing tile "pyramids", one file per pyramid (these files can be produced with proland::preprocessDem and proland::preprocessSphericalDem - see also the "preprocess" example)
function createDemFramebuffer(demTexture: Texture2D, layerTexture: Texture2D): FrameBuffer { let tileWidth = demTexture.getWidth(); let frameBuffer = new FrameBuffer(); frameBuffer.setReadBuffer(BufferId.COLOR0); frameBuffer.setDrawBuffers([BufferId.COLOR0]); frameBuffer.setViewport(0, 0, tileWidth, tileWidth); frameBuffer.setTexture2DBuffer(BufferId.COLOR0, demTexture, 0); if (layerTexture != null) { // let depthBuffer = new RenderBuffer(RenderBufferFormat.DEPTH_COMPONENT32, tileWidth, tileWidth); // **Note**: here we use the format depth_component32f because the integer version is not available in webgl2 let depthBuffer = new RenderBuffer(RenderBufferFormat.DEPTH_COMPONENT32F, tileWidth, tileWidth); frameBuffer.setTexture2DBuffer(BufferId.COLOR1, layerTexture, 0); frameBuffer.setRenderBuffer(BufferId.DEPTH, depthBuffer); frameBuffer.enableDepthTest(true, CompareFunction.ALWAYS); } else { frameBuffer.enableDepthTest(false); } frameBuffer.setPolygonMode(PolygonMode.FILL, PolygonMode.FILL); return frameBuffer; }
class DemFramebufferFactory extends BaseObject { // Storage for the already generated framebuffer: protected framebuffers = new Map<string, FrameBuffer>(); public constructor() { super() } // Retrieve the framebuffer for a given per of dem/layer textures: get(demTexture: Texture2D, layerTexture: Texture2D) : FrameBuffer { // get the key for those 2 textures: let id1 = demTexture?demTexture.getId():0; let id2 = layerTexture?layerTexture.getId():0; let key = `${id1}_${id2}` this.DEBUG("Retrieving FrameBuffer for key: %s", key); if(this.framebuffers.has(key)) { return this.framebuffers.get(key) } // Otherwise we need to create the framebuffer: let fb = this.createDemFramebuffer(demTexture, layerTexture); this.framebuffers.set(key, fb); return fb; } // create a framebuffer for a given pair of textures: createDemFramebuffer(demTexture: Texture2D, layerTexture: Texture2D): FrameBuffer { let tileWidth = demTexture.getWidth(); let frameBuffer = new FrameBuffer(); frameBuffer.setReadBuffer(BufferId.COLOR0); frameBuffer.setDrawBuffers([BufferId.COLOR0]); frameBuffer.setViewport(0, 0, tileWidth, tileWidth); frameBuffer.setTexture2DBuffer(BufferId.COLOR0, demTexture, 0); if (layerTexture != null) { // let depthBuffer = new RenderBuffer(RenderBufferFormat.DEPTH_COMPONENT32, tileWidth, tileWidth); // **Note**: here we use the format depth_component32f because the integer version is not available in webgl2 let depthBuffer = new RenderBuffer(RenderBufferFormat.DEPTH_COMPONENT32F, tileWidth, tileWidth); frameBuffer.setTexture2DBuffer(BufferId.COLOR1, layerTexture, 0); frameBuffer.setRenderBuffer(BufferId.DEPTH, depthBuffer); frameBuffer.enableDepthTest(true, CompareFunction.ALWAYS); } else { frameBuffer.enableDepthTest(false); } frameBuffer.setPolygonMode(PolygonMode.FILL, PolygonMode.FILL); return frameBuffer; } };
// Get or create a factory: public getOrCreateFactory<T extends Factory>(fname: string, className: new ()=>T) : T { if(this.factories.has(fname)) { return this.factories.get(fname) as T; } let fac = new className(); this.factories.set(fname, fac); return fac; }
// Below we need to create our dem framebuffer, from a demTexture and a layerTexture: this.DEBUG("Creating Framebuffer with dem texture: %s, layer texture: %s", demTexture, layerTexture); // get the factory: let factory = RenderContext.getCurrent().getOrCreateFactory("dem_framebuffer", DemFramebufferFactory) this.frameBuffer = factory.get({dem:demTexture, layer:layerTexture});
// Generate a noise texture of the given tile Width: this.DEBUG("Generating noise texture of size %dx%d", tileWidth) let noiseFactory = RenderContext.getCurrent().getOrCreateFactory("dem_noise", DemNoiseFactory); this.noiseTexture = noiseFactory.get(tileWidth);/
<tileCache name="groundElevations" scheduler="defaultScheduler"> <gpuTileStorage tileSize="101" nTiles="512" internalformat="RGB32F" format="RGB" type="FLOAT" min="LINEAR" mag="LINEAR"/> </tileCache> <elevationProducer name="groundElevations1" cache="groundElevations" noise="-140,-100,-15,-8,5,2.5,1.5,1,0.5,0.25,0.1,0.05"/>
public constructor(args: any = null) { super(); if(args != null) { this.minFilter = getTextureFilter(args.min || "NEAREST"); this.magFilter = getTextureFilter(args.mag || "LINEAR"); this.wrapS = getTextureWrap(args.wrapS || "CLAMP_TO_EDGE"); this.wrapT = getTextureWrap(args.wrapT || "CLAMP_TO_EDGE"); this.wrapR = getTextureWrap(args.wrapR || "CLAMP_TO_EDGE"); } };
So now I can instanciate with something like (this will simplify my life a little 👍! ):
let tp = new TextureParameters({min: "LINEAR", mag:"LINEAR"})
public constructor(tileSize: number, nTiles: number, internalf: TextureInternalFormat, f: TextureFormat, t: PixelType, params: TextureParameters, useTileMap: boolean = false) { super(tileSize, nTiles); // Note: the function below might be async: this.init(tileSize, nTiles, internalf, f, t, params, useTileMap); } public async init(tileSize: number, nTiles: number, internalf: TextureInternalFormat, f: TextureFormat, t: PixelType, params: TextureParameters, useTileMap: boolean) { // stuff here }
export async function createGPUTileStorage(tileSize: number, nTiles: number, internalf: TextureInternalFormat, f: TextureFormat, t: PixelType, params: TextureParameters, useTileMap: boolean = false): Promise<GPUTileStorage> { let obj = new GPUTileStorage(tileSize, nTiles); await obj.init(tileSize, nTiles, internalf, f, t, params, useTileMap); return obj; }
<tileCache name="groundElevations" scheduler="defaultScheduler"> <gpuTileStorage tileSize="101" nTiles="512" internalformat="RGB32F" format="RGB" type="FLOAT" min="LINEAR" mag="LINEAR"/> </tileCache>
// creating the ground elevations tile cache: this.DEBUG('Creating ground elevation TileCache...'); let cache1 = new TileCache(storage1, 'ground_elevations');/
<elevationProducer name="groundElevations1" cache="groundElevations" noise="-140,-100,-15,-8,5,2.5,1.5,1,0.5,0.25,0.1,0.05"/>
void ElevationProducer::init(ptr<ResourceManager> manager, Resource *r, const string &name, ptr<ResourceDescriptor> desc, const TiXmlElement *e)
method, we are only covering a small part of it in this first usage.
string upsample = "upsampleShader;"; if (e->Attribute("upsampleProg") != NULL) { upsample = r->getParameter(desc, e, "upsampleProg"); } upsampleProg = manager->loadResource(upsample).cast<Program>();
gridSize
value should be 24tileWidth
should come from the storage in the TileCache: int tileWidth = cache->getStorage()->getTileSize();
ostringstream demTex; demTex << "renderbuffer-" << tileWidth << "-RGBA32F"; demTexture = manager->loadResource(demTex.str()).cast<Texture2D>();
“renderbuffer-xxx-RGBA32F”
: but of course we don't have such resource file, so this must be autogenerated somehow, let's find out where. OK, so from the Ork framework we automatically generate a resource descriptor when we get those “renderbuffer” names: ptr<ResourceDescriptor> XMLResourceLoader::loadResource(const string &name) { time_t stamp = 0; TiXmlElement *desc = NULL; if (strncmp(name.c_str(), "renderbuffer", 12) == 0) { // resource names of the form "renderbuffer-X-Y" describe texture // resources that are not described by any file, either for the XML part // or for the binary part. The XML part is generated from the resource // name, and the binary part is NULL desc = buildTextureDescriptor(name); // ... more code here... } TiXmlElement *XMLResourceLoader::buildTextureDescriptor(const string &name) { string::size_type index1 = name.find('-', 0); string::size_type index2 = name.find('-', index1 + 1); string size = name.substr(index1 + 1, index2 - index1 - 1); string::size_type index3 = name.find('-', index2 + 1); string internalformat = name.substr(index2 + 1, index3 == string::npos ? index3 : index3 - index2 - 1); TiXmlElement *p = new TiXmlElement("texture2D"); p->SetAttribute("name", name); p->SetAttribute("internalformat", internalformat.c_str()); p->SetAttribute("width", size.c_str()); p->SetAttribute("height", size.c_str()); p->SetAttribute("format", "RED"); p->SetAttribute("type", "FLOAT"); p->SetAttribute("min", "NEAREST"); p->SetAttribute("mag", "NEAREST"); return p; }
// Note, we should also have support here for renderbuffers: if (name.startsWith('renderbuffer')) { let parts = name.split('-'); let width = Number(parts[1]); let intfmt = getTextureInternalFormat(parts[2]); let fmt = getTextureFormat('RED'); let ptype = getPixelType('FLOAT'); let tp = new TextureParameters({ min: 'NEAREST', mag: 'NEAREST' }); let tex = new Texture2D(width, width, intfmt, fmt, ptype, tp); this.textures.set(name, tex); return tex; }/
CPUBuffer.EMPTY
to cover this need: I could use that as default, but I feel that using a “null” value would be more appropriate ⇒ OK, the code above should work as expected now, and we can create our renderbuffer textures with: // Create the dem texture: let tileWidth = cache1.getStorage().getTileSize(); let demTexture = await rman.loadTexture2D(`renderbuffer-${tileWidth}-RGBA32F`); this.DEBUG(`Generated dem texture of size ${demTexture.getWidth()}x${demTexture.getHeight()}`); // Create the residual texture: let residualTex = await rman.loadTexture2D(`renderbuffer-${tileWidth}-R32F`); this.DEBUG(`Generated residual texture of size ${residualTex.getWidth()}x${residualTex.getHeight()}`);
`renderbuffer-${tileWidth}-RGBA32F-ground_elev_dem`
. I should keep that in mind (the next time I create a renderbuffer at least)// Create the elevation producer: this.DEBUG('Creating ElevationProducer...'); // Create the upsample program: let upsampleProg = await rman.loadProgram('upsampleShader;'); // Create the dem texture: let tileWidth = cache1.getStorage().getTileSize(); let demTex = await rman.loadTexture2D(`renderbuffer-${tileWidth}-RGBA32F`); this.DEBUG(`Generated dem texture of size ${demTex.getWidth()}x${demTex.getHeight()}`); // Create the residual texture: let residualTex = await rman.loadTexture2D(`renderbuffer-${tileWidth}-R32F`); this.DEBUG(`Generated residual texture of size ${residualTex.getWidth()}x${residualTex.getHeight()}`); let elevProd = new ElevationProducer( cache1, null, // residualTiles demTex, null, //layerTexture, residualTex, upsampleProg, null, // blendProg, 24, //gridSize, [-140, -100, -15, -8, 5, 2.5, 1.5, 1, 0.5, 0.25, 0.1, 0.05], //noiseAmp, false // flipDiagonals );
ShaderModule3: Vertex shader compile error: ERROR: 0:8: 'sampler2DArray' : No precision specified
precision highp float;
automatically inserted at the top of the shader, so what's happening here ?precision highp sampler2DArray;
Program3: Unsupported uniform type for coarseLevelSampler