Carmack's Virtualized Textures (QuakeCon 2007)

Started by
64 comments, last by xen2 16 years, 6 months ago
As long as the pixel shaders don't change the scene (such as depth info), you could easily make a 'software renderer' that could quickly evaluate what texture levels and resolutions are needed for each visible part of a model. It wouldn't even need to actually render anything - it just needs to do some of the calculations to determine where the polygons lie in screenspace and which mipmap should be applied. You also don't need to use a high-poly version of the model - the lowest poly version you have will probably suffice.

This avoids pretty much all the problems with things like GPU RAM being slow for the CPU to read from, etc.

On the other hand, if you were going to create a texture atlas manually each frame, you'd need either a perfectly accurate 'software renderer' or a _MUCH_ larger texture than the screen. Realistically, somewhere in middle (rather accurate statistics from the software pass, somewhat larger texture) is probably what you'd need to use.
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk
Advertisement
The very first topic in the Q&A should answer your question. Essentially, the engine has to determine what texture you need before it starts rendering the frame.
Quote:Original post by Extrarius
As long as the pixel shaders don't change the scene (such as depth info), you could easily make a 'software renderer' that could quickly evaluate what texture levels and resolutions are needed for each visible part of a model. It wouldn't even need to actually render anything - it just needs to do some of the calculations to determine where the polygons lie in screenspace and which mipmap should be applied. You also don't need to use a high-poly version of the model - the lowest poly version you have will probably suffice.

That's acutally an idea I was entertaining for quite awhile to determine visibility of each texture 'page', but I abandoned it for two reasons:
1) It'd probably just be too damn slow because you'd have to render the scene at full resolution. If your final screen is 1920x1200, you can't have 640x480 soft-rast going on because if you missed a small bit of a page, you'd probably end up with some popping going on as a polygon is revealed (e.g. if you have a simple 6-wall room with a pillar in the middle, and the camera is circle-strafing around the pillar, as the parts of the room behind the pillar are revealed you'll see accesses into parts of the virtual texutre that you haven't allocated/uploaded yet because the softrast thinks that area was occluded by the pillar).
2) Your results probably owuldn't match what the hardware/driver is going to render, so you might have some over/underestimations of waht's needed going on. Overestimation isn't that bad; so what, you load in a higher-res mip a bit early. Underestimation IS, though, because once the softrast finally decides that a higher-res mip than what's loaded in is necessary, and the video card is currently magnifying that lower-res mip alreaedy in the texture pool, you'll consistently get popin as your camera moves around the world, or triangles face the camera more and more.

By the way, one thing I should note, and I find to be a more interesting problem to solve than determining page visibility is page _arrangement_. One thing I tried, since it is the most obvious solution, is to just say "okay, I've got this 32x32 page of a texture, I'll dump it somewhere in the virtual texture pool, and my meshes will use an indirection texture to translate the coordinates to the pageI want to look up". However, that only works with point filtering. Once you start including linear filtering, yeah, you get some colour bleeding. Upgrade taht to anisotropic filtering, and things just go to hell. Because the hardware expects texture coordinates to be continuous, so that it knows what the aniso lookup pattern will be, the rendering results will be completely wrong because your texture coordinates are non-continuous. The hardware will instead stretch the aniso filtering pattern all the way across the virtual texture pool, between the page you're trying to look up and the page that (in terms of the final render) is adjacent to the one you're looking up. This thread from awhile ago was a part of my attempt to figure out what was going on: http://www.gamedev.net/community/forums/topic.asp?topic_id=393047 . These images demonstrate what was going on:

The hand-made unarranged texture I was using to test with:
Photo Sharing and Video Hosting at Photobucket
The artifact:
http://img.photobucket.com/albums/v627/Cypher19/artifactpage.jpg


The desired image (this was taken with point filtering):
http://img.photobucket.com/albums/v627/Cypher19/bestpointpage.jpg
Visualization of how the GPU is filtering those things:
http://img.photobucket.com/albums/v627/Cypher19/relthamexplan.jpg

[Edited by - Cypher19 on August 15, 2007 3:59:10 PM]
To be honest, I just don't see how it's possible to render all the static/opaque geometry (culling issues excepted) in one draw call.

I understand the theory, parsing the scene before render time, extracting which textures are in use, their priorities, their mipmap level, etc.. but from those infos, where do you go ?

To be able to render with only one draw call, you must allocate all your textures in a big virtual one. Whatever you do you are still limited by the hardware/driver restrictions. Let's take an example: your max texture resolution is 2048x2048, and you have 16 TMUs, so you've got enough "virtual" texture space for 2048x2048x16 pixels, before being forced to switch the textures (hence a new draw call).

Since Carmack specifically says that you could render the scene in one draw call, unless I'm missing something (like being able to switch a texture in the middle of a draw call), you're limited to 16 2048^2 textures. That's around 64 MB of video memory.

What if I use 300 MB of data in the frame ? How do they get allocated into 16 2048^2 textures ?

How do you handle compressed textures ?

How do you handle texture tiling ? Or has it become obsolete with Megatextures, in the way that all textures are "unique", even if you don't want them to ? Or do you leave it to the shaders to do their own tiling ? If so, that means the engine can't run without pixel shaders, so I don't see how it could be implemented on DX7 level hardware or hardware without ps 2.0.

How do you offset the texture coordinates from the original texture, to the virtual texture ? You'd have to store a matrix for each object, meaning a switch of matrix (-> new render call), or you'd have to use an ID per vertex, upload matrices as shader constants and offset the tex coords in the shader. Then you need shaders hardware, plus you are limited to the amount of constants available.

Maybe a part of the answer is allocating the textures in a virtual 3D texture instead, but you still need to update the texture coordinates of each mesh to sample the correct layer in the volumetric texture.

Food for thought..

Y.
Quote:Original post by Ysaneya

What if I use 300 MB of data in the frame ? How do they get allocated into 16 2048^2 textures ?

You only send down the fractions of each mip level that you need, basically.

Quote:How do you handle compressed textures ?

I'm guessing you mean DXTn stuff. If it's possible to send down DXTn stuff in chunks to the GPU, then I don't see how that would be an issue. For other compressed stuff, just get a high-quality JPEG, or a PNG, decompress on the CPU, and then load that uncompressed data to the GPU as necessary. (This reminds me; earlier I mentioned "a 64 MB card would be handle to handle blah blah blah", that was in terms of uncompressed data).

Quote:How do you handle texture tiling ? Or has it become obsolete with Megatextures, in the way that all textures are "unique", even if you don't want them to ? Or do you leave it to the shaders to do their own tiling ? If so, that means the engine can't run without pixel shaders, so I don't see how it could be implemented on DX7 level hardware or hardware without ps 2.0.

It's become obsolete. Infinite texture memory literally means that; go ahead and tile the texture in photoshop or using the content creation tools or whatever.

Quote:Maybe a part of the answer is allocating the textures in a virtual 3D texture instead, but you still need to update the texture coordinates of each mesh to sample the correct layer in the volumetric texture.

Even if you could, how would handle filtering between page edges?

The way I understood it, the megatexture is an all-encompassing texture atlas which contains a unique texture for every surface element in the world map. This way you can have scene painters working in the world like Carmack described. He also hinted that the current id technology doesn't enable the artists to sculpt the map the same way they're able to paint the environment as they run around the scene. I took this as an indication that during painting world geometry is static and thus the texture atlas arrangement may remain static while the artists paint texels.

If, during painting, the scene geometry needs to be modified (likely introducing new triangles) you'd have to re-arrange the texture atlas. I mean, in a system like this, you'd probably want to be able to give the system a texture budget, say 100K x 100K texels, along with the world geometry and then have the system make the best possible use of the available megatexture space (maximum texels per primitive). After that you'd give the result to the artists and let them paint.

It's clear that the whole megatexture won't fit into video memory, so, he must be maintaining a "working set" in video memory and I suppose this could be a local texture atlas which contains a superset of the textures for the visible primitives. I suppose you could maintain this working set by exploiting temporal coherence between frames and only update it as new textures (primitives) become visible.

Carmack also mentioned something about a test scene with a huge 100K x 100K megatexture. That pretty much means he's paging texture data in from disk. That'd would make a three level paging scheme disk<->main memory<->video memory.

I wouldn't know about the specifics of an actual implementation as far as GPU programming goes, though.

Anyway, interesting stuff indeed.

-- Jani
Quote:Original post by Ysaneya
To be honest, I just don't see how it's possible to render all the static/opaque geometry (culling issues excepted) in one draw call.

I understand the theory, parsing the scene before render time, extracting which textures are in use, their priorities, their mipmap level, etc.. but from those infos, where do you go ?

To be able to render with only one draw call, you must allocate all your textures in a big virtual one. Whatever you do you are still limited by the hardware/driver restrictions. Let's take an example: your max texture resolution is 2048x2048, and you have 16 TMUs, so you've got enough "virtual" texture space for 2048x2048x16 pixels, before being forced to switch the textures (hence a new draw call).

Since Carmack specifically says that you could render the scene in one draw call, unless I'm missing something (like being able to switch a texture in the middle of a draw call), you're limited to 16 2048^2 textures. That's around 64 MB of video memory.

The whole scene/level (static part) has one huge texture applied. I believe the demo level he has shown is using 128000x128000 texture. I am not sure how he unwraps all the data but it is all spacially coherent. So the terrain and whatever is ontop of it at some spot is stored very closly in the MegaTexture.

That huge texture obviously doesn't fit in RAM so he pages it in when needed. He could be using a 2048x2048 texture for the highest detail, 0 to 10m from the viewer. Then another 2048x2048 for the 10-50 meters and maybe a third and fourth for 50+ meters. The fragment shader then uses the 3 or 4 diffuse textures (he would also need the normal map and maybe some other textures), morphs the coords as needed and blends in between them. Check out clipmaps, very similar.

MegaTexture also stores the normal maps and other data, I believe some physics parameters...

Quote:How do you handle compressed textures ?

MegaTexture is compressed, a lot. It's all one texture in the end, doesn't matter how individual textures are handled pre level compile.

Quote:How do you handle texture tiling ? Or has it become obsolete with Megatextures, in the way that all textures are "unique", even if you don't want them to ? Or do you leave it to the shaders to do their own tiling ?

Tiling gets compiled into the one huge MegaTexture.

Don't know if this is covered in the keynote, haven't watched it yet but checkout the idTech5 walkthrough videos, especially the Tools video.

Part 1
Part 2
Tools

From everything I've read (all I could find on MegaTextures) and watched (Carmacks speeches) the ground is made up of a massive texture, 128,000x128,000 (of course artists decides what size is needed, it can be smaller but not sure if this is the max size) pixels and is one continuous texture on the hard-drive that is paged in as needed. Also, each object in the world is made up of a MegaTexture, so the guys in the game. But I'm quite positive they're a lot smaller textures (guess here), say 2048x2048.

It also seems from what he has said, that the ultra high resolution texture is only used say right under the person, while lower quality is used for things further away and quality, seems to be on the fly, is degraded as you get further and further out (automatic mip-mapping). Since this works on the PS3 which has lower memory then the 360 that means that the high resolution maps will likely be lower resolution then on say the 360 which might be lower then on the PC. Seems like they're calculated in real time.

I believe the texture is created based on all the textures determined to be needed. These are then put into a large block of memory and uploaded. This is updated in real time for the sub parts that change from frame to frame and the changes uploaded to the GPU (likely not the whole texture just the sub sections). This massive texture is made up of static geometry's texture data and dynamic (say a person in game). Data is uploaded to the shader telling it where its texture(s) are in the larger texture.

It's a really interesting concept. The tools video from QuakeCon showed a lot of information. Like the texture could be tiled but that each pixel is saved, if you wanted to stamp out the ground with set tiles you could then custom paint between them, etc. They also had brushes like in MS Paint, you could resize etc.

I'm actually working on something similar, been for a few months but in the process of putting our house on the market so I have so little spare times its not even funny. I had a demo with a 8GB "generated" texture I was streaming from but the HD it was on died a very painful death and I did not, like an idiot, have that project backed up. I was just texturing static terrain and it looked pretty darn good and worked very well. Took me about a week to implement after I spent a week reading everything I could find where Carmack discussed it.

I do want to know what type of compression is used, from 80GB to 2 DVDs is pretty impressive. I was just using raw data myself.

Edit:

@nts -- That rocks, same basic information typed around the same basic time. When I hit reply I didn't see your posting. Awesome links!

[Edited by - Mike2343 on August 15, 2007 7:17:24 PM]

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin


I find this all very confusing. I share Ysaneya's questions and I'm not even sure I understand the basic concept, much less how the implementation should work. Going from this part of the transcription, I got a dim idea of how it works in my mind. Could any of you verify if I'm on the right track?

Quote:And you can wind up taking, you know, in this room you would have the podiums, the floors, the stands, the lights. All of these things wind up, while they're separate models and you can move them around and position them separately, but when you're ready to "go fast" they all get built into the same model. So you've got one draw call because they all use the same virtual materials on there.


It sounds like it's mainly meant to work for relatively static objects. At some point during build-time or sparsely at runtime when some scene has been loaded or altered, all static objects are accumulated into one big batch buffer. To make this work for multiple objects/textures, in bare essence an UVW map is generated for this entire buffer, unwrapping the per-object textures to the scene-wide 'megatexture'. Is that the basic idea of this technique?

Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Quite interesting related stuff:
http://www-evasion.imag.fr/Publications/2004/LDN04/

This topic is closed to new replies.

Advertisement