Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a way to upload an array of bitmaps for textures with custom mipmap levels #537

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

zeh
Copy link
Contributor

@zeh zeh commented Apr 30, 2014

This patch allows a user to upload custom mipmaps to be used for a texture. Custom mipmaps are useful when creating textures that render different content depending on the resolution it is being drawn. In my own case, I'm using textures with custom mipmaps to simulate lines with a uniform thickness: whatever the texture of my quad, the lines drawn on it have the same thickness. Such a solution is best used when combined with trilinear filtering, since it interpolates the texture between different mimap levels.

This patch doesn't break compatibility with the current version of Starling. It adds two new methods:

Texture.fromBitmapDatas(datas:Array of BitmapData)
and
<ConcreteTexture>.uploadBitmapDatas(datas:Array of BitmapData, optimizeForRenderToTexture:Boolean=false, scale:Number=1, format:String="bgra", repeat:Boolean=false)

It also changes <ConcreteTexture>.uploadBitmapData() to call <ConcreteTexture>.uploadBitmapDatas() to avoid duplicating code.

In a nutshell, the new uploadBitmapDatas() works the same way as the old uploadBitmapData(). The difference is that it uses the first BitmapData instance in the array as the main texture, and subsequent instances as mipmap levels for it. It would work like this:

var texture:Texture = Texture.fromBitmapDatas([bitmap256x256, bitmap128x128, bitmap64x64, ...]);

It continues using bitmaps (it assumes each subsequent bitmap is half the resolution of the previous) until there's no more bitmapdatas available. When this happens, it falls back to resizing the last image and using it as the next mipmap level (again, similar to what the original uploadBitmapData() does).

I'm open to suggestions of a different interface. I have been using this patch on a current project and it plays well with normal Starling content.

Other details:

  • This patch does not make any attempt to hook into the asset manager and allow upload of bitmaps for separate mipmap levels. I don't use the AssetManager that much and I don't think I'd come up with the ideal interface for it.

@zeh zeh changed the title Added a way to upload an array of bitmaps for textures with custom mipmap levels with Texture.uploadBitmapDatas() Added a way to upload an array of bitmaps for textures with custom mipmap levels Apr 30, 2014
@zeh zeh closed this Apr 30, 2014
@zeh
Copy link
Contributor Author

zeh commented Apr 30, 2014

Rewriting description

@zeh
Copy link
Contributor Author

zeh commented Apr 30, 2014

Fixed description (I had swapped the class names, oops)

@zeh zeh reopened this Apr 30, 2014
@PrimaryFeather
Copy link
Contributor

Thanks for the pull request, Zeh! Indeed, that looks interesting!

I guess you're using this to continually enhance texture quality while the game is loading? Or what exactly is your use-case?

@zeh
Copy link
Contributor Author

zeh commented May 1, 2014

Hey Daniel,

It's not really while something is loading, but to produce different images depending on the size the texture is being drawn. Adaptive texture depending on the drawn size. My current use case is to make stroke widths of a vector drawing uniform, even if they're still static textures and not really a vector drawing anymore.

I can't show pictures of it because this is still under NDAs, but image I have a single texture. This texture contains a circle with a given stroke width (say, 2 pixels). I have about a dozen quads of different sizes on the screen all using the same texture. The catch is that I want all quads on the screen to have a stroke width that looks like 2 pixels, regardless of their size.

Since my texture content is dynamically created when the interface starts, what I do is create a version of the bitmapdata at 256x256 with a stroke width of 2px; create another version at 128x128, also with a stroke width of 2px; another one at 64x64 with the same stroke width; and so on and so forth. Then I create a texture out of it, using the custom mipmaps.

So when the quad gets scaled with trilinear filtering for the texture, it'll always display a version where the stroke width will be close to 2px wide. For example, a quad at 192x192px will display the 256x256 and the 128x128 textures at 50% opacity each; at that size, one texture would have the stroke at 1.5px width and the other one at 3px width, and with each at 50% opacity, it'll be close enough to looking like it's a 2px line.

I'm probably terrible at explaining this, so to put in other way:

If I created a texture with a circle and with normal/automatic mipmaps, this is what it'd look like:

Auto mipmaps

This is correct behavior for the vast majority of cases, of course. However, for my use case, I needed this:

Custom mipmaps

Of course, that is just one of the uses of custom mipmaps, but that's the use case that prompted that change to my code. I can't find much material online to help justify the needs, but in my case it was an actual design requirement (uniform strokes depending on the size of the element) and it works super well in the end. The final product in my case it is not mathematically perfect (since it's interpolating between two different textures to produce something that looks the right width) but it's close enough and virtually indistinguishable from the right thing.

@PrimaryFeather
Copy link
Contributor

Awe-some! Totally makes sense — a great way to make use of mip maps, I didn't think of that use-case before! Thanks for taking the time to explain it to me.

I'll definitely add that — just give me a little time, I'd rather merge that after the 1.5 release. I'll look at it in detail then.

Thanks again! 😄

@kheftel-old
Copy link

Wow, this is super cool! I'm interested to learn more about how/why using tri-linear filtering causes two mipmaps to be shown, each at 50% opacity. Care to explain that a little more?

@zeh
Copy link
Contributor Author

zeh commented May 1, 2014

@daniel: No problem! I only submitted this now because 1.5 is nearly done (sorry, I thought the 1.5 binary was already compiled), so this could be discussed/changed for the next version with no rush.

@kheftel: think that bilinear filtering is making sure that the texture is drawn with linear filtering in 2d - meaning, the image is correctly interpolated if it is rotated, etc. So that's what we'd call smoothing. Trilinear filtering is similar, but it uses available mipmaps to make sure that scaling also uses interpolation between the two nearest mipmaps (that is, a image drawn at 192x192 would use both the 256x256 and the 128x128 versions of the texture).

In my above case, with the custom mipmaps, if one was using bilinear filtering, as a quad was scaling down, its stroke would start shrinking in width a little bit and then immediately snap into the bigger width, as the mipmap being used was swapped. It'd be a kind of jarring effect. Trilinear filtering guarantees it'll be a smooth transition between the two mipmaps instead.

This is a good example of the effect, albeit with a different use (perspective):
http://img.tomshardware.com/us/2004/06/03/ati/pic07.jpg

On the left side it's very visible when the rendered mipmap is switched, and on the right side it's less so because it's interpolating between the two.

@kheftel-old
Copy link

Wow, that's awesome! Thanks for explaining it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants