|
View previous topic :: View next topic
|
| Author |
Message |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Sat Nov 28, 2009 4:27 pm Post subject: Introducing LibVT |
 |
|
hello all
inspired by "Megatexture v2" and encouraged by Sean Barrett's SVT implementation i went on to create a similar virtual texturing implementation. i have been working on my LibVT as part of my university practical course and am now continuing the work as the diploma thesis.
i'm using the ideas and some shader code from Sean Barrett's SVT (thanks!), but the non-shader code is newly-written and has some significant differences to SVT:
• While SVT works with procedurally generated textures, LibVT does texture streaming of preexisting textures. A preprocessing tool converts textures of size 8k^2 - 128k^2 into equally sized tiles and their mip-chain-tiles. Texture streaming happens asynchronously in another thread (which complicates things wrt locking etc). There is also a RAM cache for pages.
• LibVT is heavily configurable:
* Texture stores on the disk can be bmp, jpg, png
* Linear and Bilinear filtering supported, i'm currently working on trilinear filtering
* The page dimension is configurable from 64^2 - 512^2
* This means there is also a non-fixed size of the mip-chain-length. Currently 9 is the maximum, though, (128k^2 megatexture with 512^2 tiles or a 64k^2 texture with 256^2 ....)
* Size of the physical texture, RAM cache, readback reduction shift are all configurable
* There is a option to keep the X highest mip-map levels in VRAM all the time
* Readback of the tile-determination-pre-pass has 4 modes: kBackbufferReadPixels (good idea), kBackbufferGetTexImage (probably dumb), kFBOReadPixels, kFBOGetTexImage (good idea)
* Readback of the tile-determination-pre-pass can happen asynchronously with PBOs
* Experimental asynchronous page table updates using PBOs
* Option to do tile-determination pre-pass by sampling from a texture instead of doing calculation
• LibVT currently uses looping in the pixel shader instead of providing fallback entries in the page table for not-available tiles, which simplifies the page table management. Interestingly the looping in the pixel shader exposed some fundamental bugs in the NVIDIA GLSL driver, which seem to have been fixed only in their most recent 190.x releases.
• Since the page-table size is max. 256x256 at the time and there are no fallback entries, the page table upload has not been a performance concern. Thus there is no fine grained page-table updating as in SVT, but only a min-row, max-row upload or skipping if there are no updates at all.
• LibVT currently allows only one virtual texture, mainly because i've not yet seen a good reason to allow multiple ones.
• LibVT is written in C with a bit of C++ for the data structures and threading. It has been tested on Mac OS X and Windows with NVIDIA hardware
• LibVT uses boost::threads and DevIL for image loading (only on Win32)
• LibVT is architected to be easily usable by existing applications and to be small and comprehensible. Although some recent features added some cruft it is still a fraction of the size of the comparable SVT code.
if there is interest i can look into providing the source code for LibVT. providing binaries for the testapp is probably more complicated since it would also require the "megatexture" which is several hundred MB (for a small version) and consists also of copyrighted material. also, the engine and testapp using LibVT is written in Objective-C for the Mac, although it can be cross-compiled for windows.
throughout the diploma thesis i will investigate additional features like longer mip chains, anisotropic filtering, decaling, using GL_EXT_texture_array for the physical texture so borders aren't neccesary, using multiple textures for the physical texture, delaying readback until the next frame, DXT compression, additional data like spec or normals, different tile determination e.g. analytic or renderToUVspace, Probabilistic loading of adjacent pages, combining the readback pass with early-Z or pre-pass in a deferred shader, using GL_ARB_texture_query_lod in the pre-pass, converting the pre-pass data using CUDA / OpenCL, more intelligent RAM caching, prioritized page loading, fine grained page table updates, different texture formats for page table and readback texture, automatically adjusting LOD bias to make working set fit (ID SOFTWARE), preventing LOD snap-in by using fading or blending of the physical texture (ID SOFTWARE), etc.
i'll also make extended performance testing of current and future options.
other ideas for the virtual texturing include using it for shadowmapping or for backing a virtual heightmap which is used with VTF and displacement in the vertex shader for LOD-terrain.
anyway, i also have some questions
• in the shader code from SVT i'm not sure why sometimes 255.0 and sometimes 256.0 is used - some example lines:
page_pos = floor(page.rg * 255.0 + 0.5);
vec2 page = floor(gl_TexCoord[0].xy * page_table_size)/256.0;
result.rg = floor(page_low * 256.0 + 0.5) / 255.0;
float mip2 = floor(exp2(page.b * 255.0) + 0.5);
• i'm currently implementing trilinear filtering and sean mentioned that he has to do a 2px border, which of course is evident to me. however i currently have a 1px border (on all sides) and i'm seeing ZERO artifacts. i even did a test-megatexture with large single colored areas to make artifacts quickly visible and there are none! so, bilinear filtering without border has big artifacts, but bilinear OR TRILINEAR with just 1px border and there are no artifacts!?
• many other questions i've forgotten but now that this thread is started i can just reply
• from sean's shader documentation "note that this totally hoses using S3TC: we need both mip levels to start on a block boundary, but we also want to make mip level 1 match other surfaces, so we need mip level 1 to start on a world-aligned 4x4 block, which means it must be world-aligned 8x8 at mip level 0. that means we need to pad mip-level 0 with an 8x8 block on all sides, so e.g. use 112x112 out of 128x128! (And I'm not sure that's even workable as you recurse down... I think you have to use 64x64 out of 128x128 to make everything line up top to bottom.)"
this totally confuses me. ;-> the physical texture only has one mip-level (or 2 in the trilinear case), so what alignment are we talking about here? also it seems like ID is using borders and DXT without problems
btw. i found a semi-recent update on the original ID implementation:
http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf
does anybody have a idea if it is even possible to get a video of this siggraph course? i loaned the siggraph dvds last year but they didn't even contain the relevant material (Beyond Programmable Shading).
and the blog of another guy who is also implemeting Megatexture/SVT: http://sandervanrossen.blogspot.com/
thanks again sean (the video and sample code really helped me start)! thanks for all possible answers/replies as well
bye, julian
P.S. please forgive spelling mistakes, i'm not a native english speaker |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Sat Nov 28, 2009 5:40 pm Post subject: Re: Introducing LibVT |
 |
|
| julian wrote: |
• many other questions i've forgotten but now that this thread is started i can just reply
|
ok here is one: in the texture atlas documentation nvidia mentiones that it is possible to do bilinear filtering (of one mip level) without borders.
Nvidia's "Batching via Texture Atlases"
first:"DirectX defines texture coordinates 0 and 1 to coincide, i.e., a vertex with texture coordinates (0, 0) and a vertex with texture coordinates (1, 1) both access the exact same texel. To access all texels of a texture of dimensions width by height once and only once, models need to use u-coordinates in the range [.5/width, 1-.5/width] and v-coordinates in the range [.5/height, 1-.5/height].
Most applications, however, use texture coordinates ranging from zero to one, inclusive, nonetheless. While such coordinates actually wrap, clamp, or mirror a texture by exactly one texel, the benefit of being texture-dimension independent outweighs the slight image-quality reduction."
is this the very same in OpenGL?
then:
"Sampling a texel at its center means that even when bilinear filtering is enabled that only that texel contributes to the filtered output. [...] bilinear filtering of the highest resolution mip-level is thus safe [...]"
since we have only one mip map level in the bilinear case, can't we do the very same thing to get bilinear filtering without borders at all? we have to adjust texture coordinates anyway, so when adjusting them to go only to the center of the pixels on the edge of the pages, the filtering will never pull from neighboring pages!? |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Sat Nov 28, 2009 9:44 pm Post subject: Re: Introducing LibVT |
 |
|
| julian wrote: | anyway, i also have some questions ;-)
• in the shader code from SVT i'm not sure why sometimes 255.0 and sometimes 256.0 is used - some example lines:
page_pos = floor(page.rg * 255.0 + 0.5);
vec2 page = floor(gl_TexCoord[0].xy * page_table_size)/256.0;
result.rg = floor(page_low * 256.0 + 0.5) / 255.0;
float mip2 = floor(exp2(page.b * 255.0) + 0.5); |
I'm not sure why it's inconsistent. Generally, there's a thing where you store an integer from 0..255 in a texture, which opengl samples to a number that is supposedly 0 to 1. Ideally you'd convert back to the original by using x*255. However, there's no guarantee that that produces the original integer, because the conversion from 0..255 to 0..1 may be an approximation. So floor(x*255 + 0.5) rounds that to the nearest integer, on the assumption that any reasonable hardware will at least be within +-0.5 after that.
On the other hand, if you want to sample from a texture and you have an integer coordinate from 0..255, then you want to divide that by 256 (and then offset it a bit), which I think is why page divides by 256 (I haven't checked the source). The mip2 calculation is kind of arbitrary so I just multiply by 255 and don't bother with rounding at all. I'm not sure about result.rg.
| Quote: | | i'm currently implementing trilinear filtering and sean mentioned that he has to do a 2px border, which of course is evident to me. however i currently have a 1px border (on all sides) and i'm seeing ZERO artifacts. |
If you use a physical texture with two mipmap levels, the lower-resolution level gets either a 0px border or a 1px border. This leads to a 2-px border in the higher-res texture level. You'll never touch the 2nd pixel of the higher-res texture border, you just need it to be there to line-up with the lower-res texture border.
However, as I mention somewhere, you actually shouldn't need to pad on all sides, you should only need to pad on, say, the top and left but not bottom and right. Thus you should be able to get away with the lower-res texture having a total padding of 1 texel between tiles instead of 2 (1 on left and 1 on right), and thus 2 texels total at the higher-res (instead of 4, 2 on left and 2 on right).
Otherwise, I dunno.
| Quote: | from sean's shader documentation "note that this totally hoses using S3TC: we need both mip levels to start on a block boundary, but we also want to make mip level 1 match other surfaces, so we need mip level 1 to start on a world-aligned 4x4 block [...]
this totally confuses me. ;-> the physical texture only has one mip-level (or 2 in the trilinear case), so what alignment are we talking about here? also it seems like ID is using borders and DXT without problems |
This is only talking about the trilinear case. My guess is that if you use YCoCg you're far less likely to get artifacts because YCoCg does a much better job of preserving the original colors.
Let's say we've got this texture on the hard drive that's 64k by 64k texels. Suppose that the way things are supposed to work is that there's a tile J whose bottom right corner samples from texel (31,23) in the big main texture.
The next tile down and to the right from it, I'll call tile K, "starts" at texel (32,24). However, due to bilinear sampling, it needs to sample from texel (31,23) as well, so we'll have to add the border texel of (31,23).
Because DXT compresses a color in a context-dependent way, the same input color can compress to different output colors in different blocks. This means that texel (31,31), which appears in both tile J and tile K, will compress to different colors in those two tiles unless it appears in the exact same block in both tiles.
For it to appear in the exact same block in both tiles, you have to pad with a full tile. (E.g. in tile J it appears in the block [(28,20)...(31,23)]; so if you need to pad tile K with texel (31,23), you need to include the full 4x4 block [(28,20)...(31,23)] as padding to make sure you get the exact same color for pixel (31,23).
This requires you to pad by an entire DXT block. As noted before, you can actually get away with only padding on one side (except if you want anistropic filtering).
Now, trilinear complicates this, and you can work through the same sort of logic as above, and it gets wonky. The basic issue is that if you're drawing virtual mip level 2 (which means you have a page with mip level 2 in it, and the mipmapped page level contains mip level 3), and you're just about to get further away so the trilinear weighting is basically 100% the lower-res texture (mip level 3), and then you move just a little bit away and now the pixel switches to virtual mip level 3 (which means you want a page with mip level 3 in it, and the mipmapped page level contains mip level 4), you again want that transition to use the exact same blocks so that there isn't a pop. But that means the mipmapped page level DXT blocks, with padding, needs to exactly line up with the next level's natural blocks.
The ramifications of that are needing a padding of a 4x4 block on all sides of the mipmapped level which means needing 8x8 blocks on all sides of the non-mipmapped level. (You won't use the outer blocks at all, but you need to allocate room for them so they line up with the mipmapped page.) Not having coded it, I'm not positive what constraint it actually forces if you follow that top to bottom.
| Quote: | | Nvidia's "Batching via Texture Atlases" |
This is addressing a different problem. They are worried about padding textures to avoid artifacts caused by sampling "bad looking" data. We are interested in the original application of texture borders defined by OpenGL, seamlessly texturing at the boundary between two different textures (in our case, both textures are stored in the same physical texture, but we draw them independently and they are not continuous in texture space).
In fact, the shader already does the stuff talked about above; it is aware of exactly where the physical textures lie and scales its coordinates to exactly match them. But the upshot is that tile J needs to sample from a texel that encodes the virtual texture's texel (31,23), and K needs to sample from a texel that encodes the virtual texture's texel (31,23), but J and K are not adjacent in memory. It is just the inherent nature of bilerp that you need multiple copies of the texels that lie along shared boundaries. |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Sun Nov 29, 2009 3:10 pm Post subject: Re: Introducing LibVT |
 |
|
first of all, thank you very much for your detailed answer!
| sean wrote: |
I'm not sure why it's inconsistent. Generally, there's a thing where you store an integer from 0..255 in a texture, which opengl samples to a number that is supposedly 0 to 1. Ideally you'd convert back to the original by using x*255. However, there's no guarantee that that produces the original integer, because the conversion from 0..255 to 0..1 may be an approximation. So floor(x*255 + 0.5) rounds that to the nearest integer, on the assumption that any reasonable hardware will at least be within +-0.5 after that.
|
i'll do some tests here
| sean wrote: |
On the other hand, if you want to sample from a texture and you have an integer coordinate from 0..255, then you want to divide that by 256 (and then offset it a bit), which I think is why page divides by 256 (I haven't checked the source). The mip2 calculation is kind of arbitrary so I just multiply by 255 and don't bother with rounding at all. I'm not sure about result.rg.
|
ok thx.
| sean wrote: |
However, as I mention somewhere, you actually shouldn't need to pad on all sides, you should only need to pad on, say, the top and left but not bottom and right. Thus you should be able to get away with the lower-res texture having a total padding of 1 texel between tiles instead of 2 (1 on left and 1 on right), and thus 2 texels total at the higher-res (instead of 4, 2 on left and 2 on right).
Otherwise, I dunno.
|
I'm gonna do some detailed tests here as well
| sean wrote: |
This is only talking about the trilinear case. My guess is that if you use YCoCg you're far less likely to get artifacts because YCoCg does a much better job of preserving the original colors.
Let's say we've got this texture on the hard drive that's 64k by 64k texels. Suppose that the way things are supposed to work is that there's a tile J whose bottom right corner samples from texel (31,23) in the big main texture.
The next tile down and to the right from it, I'll call tile K, "starts" at texel (32,24). However, due to bilinear sampling, it needs to sample from texel (31,23) as well, so we'll have to add the border texel of (31,23).
Because DXT compresses a color in a context-dependent way, the same input color can compress to different output colors in different blocks. This means that texel (31,31), which appears in both tile J and tile K, will compress to different colors in those two tiles unless it appears in the exact same block in both tiles.
For it to appear in the exact same block in both tiles, you have to pad with a full tile. (E.g. in tile J it appears in the block [(28,20)...(31,23)]; so if you need to pad tile K with texel (31,23), you need to include the full 4x4 block [(28,20)...(31,23)] as padding to make sure you get the exact same color for pixel (31,23).
This requires you to pad by an entire DXT block. As noted before, you can actually get away with only padding on one side (except if you want anistropic filtering).
Now, trilinear complicates this, and you can work through the same sort of logic as above, and it gets wonky. The basic issue is that if you're drawing virtual mip level 2 (which means you have a page with mip level 2 in it, and the mipmapped page level contains mip level 3), and you're just about to get further away so the trilinear weighting is basically 100% the lower-res texture (mip level 3), and then you move just a little bit away and now the pixel switches to virtual mip level 3 (which means you want a page with mip level 3 in it, and the mipmapped page level contains mip level 4), you again want that transition to use the exact same blocks so that there isn't a pop. But that means the mipmapped page level DXT blocks, with padding, needs to exactly line up with the next level's natural blocks.
The ramifications of that are needing a padding of a 4x4 block on all sides of the mipmapped level which means needing 8x8 blocks on all sides of the non-mipmapped level. (You won't use the outer blocks at all, but you need to allocate room for them so they line up with the mipmapped page.) Not having coded it, I'm not positive what constraint it actually forces if you follow that top to bottom.
|
thanks! i'm gonna have to think about this a while
| sean wrote: |
This is addressing a different problem. They are worried about padding textures to avoid artifacts caused by sampling "bad looking" data. We are interested in the original application of texture borders defined by OpenGL, seamlessly texturing at the boundary between two different textures (in our case, both textures are stored in the same physical texture, but we draw them independently and they are not continuous in texture space).
|
maybe i'm confused but i don't think you are right here.
they are worried about two things:
• pollution of the lower mip-levels because downsampling pulls in from neighboring textures. they solve this by placing textures only at power-of-two boundaries. anyway this doesn't concern us at all.
• filtering pulling in texels from neighboring textures at texture-boundaries. this is *exactly* the same problem we are having. the difference is just that they have a full mip chain of their big-physical texture and adjust their coordinates in the preprocessing, while we adjust the coordinates at runtime because the physical texture is dynamic and containes tiles from different mip levels.
| sean wrote: |
In fact, the shader already does the stuff talked about above; it is aware of exactly where the physical textures lie and scales its coordinates to exactly match them. But the upshot is that tile J needs to sample from a texel that encodes the virtual texture's texel (31,23), and K needs to sample from a texel that encodes the virtual texture's texel (31,23), but J and K are not adjacent in memory. It is just the inherent nature of bilerp that you need multiple copies of the texels that lie along shared boundaries. |
i see that, but it missed the point.
"Sampling a texel at its center means that even when bilinear filtering is enabled that only that texel contributes to the filtered output."
currently we add a border and then contract the withinPage Coordinates:
withinPageCoord = withinPageCoord * page_size_minus2/page_size + 1.0/page_size;
we could *not add the border* but still contract the withinPageCoord so it goes from 0.5 to page_size_minus_0.5 instead of from 0.0 to page_size. this way the bilinear filtering of the texels at the page boundaries would not pull in from adjacent pixels that belong to other pages. sounds reasonable?
thanks, julian |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Sun Nov 29, 2009 7:01 pm Post subject: Re: Introducing LibVT |
 |
|
| julian wrote: | | this way the bilinear filtering of the texels at the page boundaries would not pull in from adjacent pixels that belong to other pages. |
it seems to work insofar as the filtering doesn't pull in from neighboring pages, but since the border pixels only contribute to an area of half the size the result is distorted in this way... |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Sun Nov 29, 2009 8:00 pm Post subject: |
 |
|
I think you're confusing two sense of "adjacent pages".
| Quote: | they are worried about two things:
• pollution of the lower mip-levels because downsampling pulls in from neighboring textures. they solve this by placing textures only at power-of-two boundaries. anyway this doesn't concern us at all.
• filtering pulling in texels from neighboring textures at texture-boundaries. this is *exactly* the same problem we are having. |
This is not the same problem we are having (at least, my implementation of SVT doesn't). To rephrase what you just said, they are worried about contamination of samples from physical tiles by texels in adjacent physical tiles. We are worried about the seams between adjacent virtual tiles, which is not a concept that document is even considering.
Page J and page K are adjacent in the virtual texture; they are not adjacent in the physical texture.
Texels at the boundary between virtual pages must be in both pages, or else you'll get a seam on bilinear sampling. You don't have to think of this as "padding" or a "border", you can just think of this as "every tile is slightly overlapping the next tile".
Within sampling from a tile, you want to do that "contraction" thing to make sure you sample from exactly within the physical tile and don't get outside of it.
That "contraction" is already implemented in the SVT shader, and the bilinear-implied overlap is implemented in SVT and sometimes called "padding" or a "border" because the conception of a "natural" tile is ones that are non-overlapping (which you could use for nearest-neighbor-sampled SVT). This border doesn't exist to deal with "accidentally" sampling outside of the physical tile, but to make it so that correctly sampling the physical tile produces consistent results along the edges of adjacent virtual tiles.
For instance, the NVIDIA paper you linked gives lip service to the wrap/mirror filter, but doesn't actually describe how the mechanics of bilerp filtering should work with wrap (the only way you could make it work without padding would be to fetch 4 texels from the texture map and bilerp them yourself). Eventually they do say:
| Quote: | | Adding border texels to a texture is a possible solution to reducing artifacts due to texture filtering. |
This is exactly what we're doing; the artifact they're talking about is because they're moving through the virtual texture space from one copy to the next wrapped copy, and normal bilinear-filtered texture mapping blends in a certain way the texels of that transition; we're doing the same thing, but not moving between two copies of that texture wrapped, but two adjacent-in-virtual-space texture tiles. |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Tue Dec 01, 2009 6:36 pm Post subject: |
 |
|
| julian wrote: |
| sean wrote: |
I'm not sure why it's inconsistent. Generally, there's a thing where you store an integer from 0..255 in a texture, which opengl samples to a number that is supposedly 0 to 1. Ideally you'd convert back to the original by using x*255. However, there's no guarantee that that produces the original integer, because the conversion from 0..255 to 0..1 may be an approximation. So floor(x*255 + 0.5) rounds that to the nearest integer, on the assumption that any reasonable hardware will at least be within +-0.5 after that.
|
i'll do some tests here
|
testing shows that current mac/windows NVIDIA and mac ATI drivers don't need that. (x*255) works 100% fine. of course there may be old drivers out there that need this...
| julian wrote: |
| sean wrote: |
On the other hand, if you want to sample from a texture and you have an integer coordinate from 0..255, then you want to divide that by 256 (and then offset it a bit), which I think is why page divides by 256 (I haven't checked the source). The mip2 calculation is kind of arbitrary so I just multiply by 255 and don't bother with rounding at all. I'm not sure about result.rg.
|
ok thx.
|
testing shows that this line is also superfluous:
result.rg = floor(page * 256.0 + 0.5) / 255.0;
writing just:
result.rg = page;
instead gives the exact same results after readback on the CPU (although the floating point values differ by up to 0.25).
| sean wrote: |
However, as I mention somewhere, you actually shouldn't need to pad on all sides, you should only need to pad on, say, the top and left but not bottom and right.
|
although a 1px border on two adjacent sides leads to every tile having a border on ALL sides (in the physical texture), it is not clear to me why this should work in our case. the border on the 2 other sides would still come from unrelated pages...
| sean wrote: | | I think you're confusing two sense of "adjacent pages". |
i think i was always taking about physically adjacent pages.
| sean wrote: |
This is not the same problem we are having (at least, my implementation of SVT doesn't). To rephrase what you just said, they are worried about contamination of samples from physical tiles by texels in adjacent physical tiles. We are worried about the seams between adjacent virtual tiles, which is not a concept that document is even considering. |
the funny thing is, i really was worried about "contamination of samples from physical tiles by texels in adjacent physical tiles" and not the seams between "adjacent virtual tiles". of course this contamination is also a problem in our case, it happens if we do bilinear filtering without adding a border. thanks for bringing to my attention that these seems between adjacent virtual tiles are an additional related problem.
| sean wrote: |
Page J and page K are adjacent in the virtual texture; they are not adjacent in the physical texture.
Texels at the boundary between virtual pages must be in both pages, or else you'll get a seam on bilinear sampling. You don't have to think of this as "padding" or a "border", you can just think of this as "every tile is slightly overlapping the next tile".
Within sampling from a tile, you want to do that "contraction" thing to make sure you sample from exactly within the physical tile and don't get outside of it.
That "contraction" is already implemented in the SVT shader, and the bilinear-implied overlap is implemented in SVT and sometimes called "padding" or a "border" because the conception of a "natural" tile is ones that are non-overlapping (which you could use for nearest-neighbor-sampled SVT). This border doesn't exist to deal with "accidentally" sampling outside of the physical tile. |
but it solves this issue as well. if you don't add the border (and do bilinear filtering) this very thing happens.
| sean wrote: |
, but to make it so that correctly sampling the physical tile produces consistent results along the edges of adjacent virtual tiles.
. |
ok, thx.
so, my suggestion of doing bilinear filtering without border by doing a 0.5px texcoord contraction works insofar as it solves issue 1.) preventing sampling from adjacent physical pages, but doesn't solve issue 2.) correct filtering across adjacent virtual pages. additionally it has the issue i mentioned with the border being only half as wide. dumb idea 
Last edited by julian on Wed Dec 09, 2009 4:09 pm; edited 1 time in total |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Tue Dec 01, 2009 6:38 pm Post subject: |
 |
|
EDIT: inserted lost part into previous post
Last edited by julian on Wed Dec 09, 2009 4:09 pm; edited 1 time in total |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Wed Dec 09, 2009 3:55 pm Post subject: |
 |
|
| just FYI, i had no success with using borders just on two sides. |
|
| Back to top |
|
 |
skynet
Joined: 01 Sep 2008 Posts: 20
|
| |
Posted: Wed Dec 09, 2009 4:32 pm Post subject: |
 |
|
That is understandable.
If you take for example a simple bilinear filter, it will fetch 4 texels around the original texture coordinate you intended to sample. Depending to which side (left/right/bottom/top) of the physical page you get close, these fetches may already reach into neighbouring virtual pages (left/right/bottom/top). If you do not provide these extra texels by adding borders, the fetches end up in completely unrelated physical pages. |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Wed Dec 09, 2009 4:35 pm Post subject: |
 |
|
that is exactly why i didn't expect it to work
i just tried because sean said it should work ;-> |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Wed Dec 09, 2009 7:10 pm Post subject: |
 |
|
You'd have to get the math just right for it to work, assuming it actually works.
Again, this is because this isn't padding for sampling 'off the edges of the tile'--you should be contracting the coordinates to always stay within the tile anyway. This is borders to make sure adjacent textures map totally identically. (Try putting sharp (pixellated) 45-degree-diagonal lines in your texture that aren't tile-aligned, and make sure they don't sawtooth or otherwise jiggle at page boudaries.)
Image your tiles are 16x16, and let's just look at one row of texels (hold y constant), so I'll only talk about the x coordinate.
In nearest neighbor filtering, page 0 covers texels 0..15, page 1 covers texels 16..31, page 2 covers texels 32..47.
In bilinear filtering, if we keep the physical pages (after bordering) at 16 texels, and we put borders on both side, page 0 covers texels 0..13, page 1 covers texels 14..27, page 2 covers texels 28..41.
If we only "border on one side", then a 16-texel page has room for 15 real texels, so page 0 covers texels 0..14, page 1 covers texels 15..29, page covers texels 30..44.
Now, the physical page for each of these will have a border on only one side, so, say, page 0 contains texels 0..15, page contains pixels 15..30, and page 2 contains texels 30..45.
Note how each page still overlaps on both sides! So you have what you need to avoid seams; you just have to get the math right.
If we stick with the 'texel-at-the-center' notation of OpenGL, the "border" between two nearest-neighbor texels lies at *integers*, and the centers are at +0.5. Therefore the bilinear "center" of each texure is at +0.5 as well--that's the point where you sample the texel purely, unweighted by adjacent texels.
So if you sample from texel 15, at the center of it (15.5) it just samples texel 15, but at the low end of it (15.001) it samples about 50% from texel 15 and 50% from texel 14; and if you sample texel 29 (remember, page 1 "covers" 15..29) you get texel 29 if you sample at 29.5, and 50% of texel 29/30 if you sample at 29.999.
Both page 0 and page 1 contain texel 15, but in terms of what bilerping they can do with texel 15, page 0 contains texel 14, and page 1 contains texel 16. So page 0 can do bilerping "left of" 15, and page 1 can do bilerping "right of" 15.
The center of 15 is actually at 15.5, and the logical tile width is 15 (remember, 15 texels + 1 border texel per page), so what we want to do is sample from page 0 for the values from 0.5 to 15.5, then from page 1 for the values from 15.5 to 30.5, then from page 2 for the values from 30.5 to 45.5, etc.
So, basically, you have to adjust the phase by 0.5 texels (relative to nearest neighbor) if you add borders on the right only. |
|
| Back to top |
|
 |
julian
Joined: 17 Oct 2008 Posts: 38
|
| |
Posted: Wed Dec 09, 2009 7:41 pm Post subject: |
 |
|
i think i have to try it again
(i with i had kept the modifications, especially for the preprocessing)
thanks for taking the time for the writeup! |
|
| Back to top |
|
 |
skynet
Joined: 01 Sep 2008 Posts: 20
|
| |
Posted: Thu Dec 10, 2009 1:15 am Post subject: |
 |
|
Sean, for the last few hours I have been trying to understand your approach. Can it be rephrased to
"If you encounter a texel T15 in virtual page 0 that needs to sample its right neighbour T16 in the next virtual page 1, turn the problem around: sample T16 in page 1 so that it needs its left neighbour T15. T15 is part of the left border of page 1, so it can be accessed." ?
I still have to come grips with the needed maths for that  |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Thu Dec 10, 2009 3:02 am Post subject: |
 |
|
Yes.
The math all depends on how you split up virtual pages. If you want your virtual pages to have *16* texels, then you need a border of one texel and use physical pages with 17 texels.
So page 0 will have, say, T0..T16, or it will have T(-1)..T15. If you pick the former, then page 0 nominally has texels 0..15, but the physical pages are 17 texels wide, so the physical page for virtual-page-0 is 0..16, that for vp1 is 16..32, for vp2 is 32..48, etc.
And then, yes. Any bilerp of a pair of pixels (x, x+1) can be satisfied by some page; if you bilerp between (15,16), that comes from page 0; if you bilerp between (16,17), that comes from page 1. If you're at exactly 16, it can come from either page successfully. (If you're at 16 +- epsilon, it can come from either page successfully, because the bilerp won't push it significantly away from the value at 16 anyway.)
If your physical texture is 1024 texels, and virtual texel 16 lands at x = 144, then the correct OpenGL & D3D texture coordinate to sample the center of texel 16 isn't s=144/1024, but s=144.5/1024. (Or "u" instead of "s" for D3D.) This is where the half-texel phase offset I was talking about comes from. |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|