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

Texenvs need to stay alive until they're drawn #36

Open
ian-h-chamberlain opened this issue Jan 7, 2024 · 2 comments
Open

Texenvs need to stay alive until they're drawn #36

ian-h-chamberlain opened this issue Jan 7, 2024 · 2 comments

Comments

@ian-h-chamberlain
Copy link
Member

ian-h-chamberlain commented Jan 7, 2024

Yeah, I saw that a little while ago. Unfortunately, the main bit of rendering a model it doesn't touch, textures, is the bit I'm having difficulty with. I've successfully got a shape with a texture, but I find myself hopelessly lost trying to understand how the texture pipeline is supposed to work and I had to go a fair bit against my understanding of how it should work in order to get two faces with two different textures.
Please enjoy this square, which took me about 9 hours to get textured:

PXL_20231223_094143210.mp4

(Sorry for the terrible recording quality. I should have turned down my screen brightness first.)
The problem I'm having is that the texture pipeline seems to persist state across draw calls (Instance::draw_arrays calls, really), even when I think it shouldn't be - first, enabling textures for only the first face and resetting the TexEnv pipeline for the second did nothing, giving me a lovely yellow square (texture coordinates (0.0, 0.0) on Bowser's face) on the back; and second, using two separate textures for the two sides gave me Peach on both sides, for some reason (the Peach texture got used for both sides instead of only the second side, as if the GPU were batching the draws and preserving only the last-written texture). I worked around the issue by merging the textures into one, using it for both draw calls, and adjusting the texture coordinates to match.
I suppose I can technically deal with this by merging all the textures for everything that's supposed to be drawn on one frame into one giant texture and using that for every shape that frame, but it feels like I'm misunderstanding something fundamental about how textures are supposed to work. Either that or it's a bug.
(By the way, having a safe wrapper for draw_elements would be pretty handy in the future for optimising the number of vertices.)


My friend figured it out, and in retrospect it's sort of obvious - textures have to be alive until they're drawn, and if they're freed before the end of the frame, strange things happen. TexEnvs still don't work how I think they do, but at least I can draw more than one texture at a time now.

Originally posted by @Jhynjhiruu in #35 (comment)

@newdev8

This comment was marked as off-topic.

@Bash-09
Copy link

Bash-09 commented Sep 19, 2024

*edit: I've since gained more of an understanding of how textures and texenvs work, and have a different solution outlined in #41 (comment) for managing this. So please go have a look there first.

Hihi.

I'm having my own look into an API for textures, however there is one particular issue that's makes it slightly difficult.

Like was mentioned earlier how a texture must stay alive until it is drawn, while a texture is bound it must be pinned and also cannot be unbound except by binding a different texture to the same texture unit (at least as far as I can tell).

Some API designs I have considered for binding textures are as follows. (Although keeping in mind these all still suffer from the issue outlined in #41, however I imagine that will be a greater fix at a later time.

  1. Binding a texture returns a guard
struct Texture { ... }

struct TextureBinding<'t> {
    tex: &'t Texture, // Holding a reference to it will effectively pin it for the duration of the guard's lifetime right?
    unit: TexUnit,
}

impl Drop for TextureBinding<'_> {
    fn drop(&mut self) {
        unsafe { C3D_UnbindTex(self.tex, self.unit); } // But this doesn't exist ;~;
    }
}

impl Instance {
    fn bind_texture<'t>(&mut self, texture: &Texture, unit: TexUnit) -> Option<TextureBinding> {
        if isBound(unit) { 
            return None 
        }

        unsafe { C3D_TexBind(texture, unit); }

        Some(TextureBinding {
            tex: texture, 
            unit,
        })
    }
}

However, since there's not currently any functionality to actually unbind a texture this doesn't really work. Perhaps at initialization of the Instance, some default texture could be initialized that can be bound over top of other textures that wish to unbind? Or functionality could be added to the C citro3d to allow unbinding a texture.

  1. Bound textures are owned by the Instance and can be replaced with others
    Textures could either be initialized into Boxes, or otherwise the Instance itself would probably have to become Pinned for it to store the textures while they're bound (although actually I'm not totally sure how that would work.. I'm not the most familiar with having to use Pin).
struct Texture { ... }

struct Instance {
    ...
    bound_textures: [Option<Pin<Box<Texture>>>; 3],
}

impl Instance {
    /// Binds a texture to the given texture unit, returning the previously bound texture if there was one
    fn bind_texture(&mut self, texture: Box<Texture>, unit: TexUnit) -> Option<Box<Texture>> {
        unsafe { C3D_TexBind(texture, unit); }
        
        let idx: usize = match unit { ... };
        self.bound_textures[idx].replace(Pin::new(idx)).map(|p| p.get_mut())
    }
}
  1. Instance keeps references to the bound textures
    This is similar to 2 but instead of taking Box<Texture> it takes &Texture, however I believe because of the lifetime annotation being added on Instance, once a texture is bound it is effectively borrowed until the Instance is dropped, preventing the Texture from being able to be dropped or modified in the meantime, even if another texture has been bound to the unit it was previously using.
struct Texture { ... }

struct Instance<'t> {
    ...
    bound_textures: [Option<&'t Texture>; 3],
}

impl<'t> Instance<'t> {
    fn bind_texture(&mut self, texture: &'t Texture, unit: TexUnit) {
        unsafe { C3D_TexBind(texture, unit); }
        
        let idx: usize = match unit { ... };
        self.bound_textures[idx] = Some(texture)
    }
}

I guess this also avoids the issue of textures being dropped before the frame finishes rendering if the Instance is keeping them alive indefinitely.

3 seems like the simplest option but also the most limited, and 1 just sort of isn't possible right now without some weird hacky stuff to have a default texture maybe? So only option 2 really works and would allow textures to be modified after they have been bound (and unbound). I didn't initially love that the textures would have to boxed, but honestly it's probably not a big deal.

If anybody has any thoughts or suggestions, please share them. Otherwise I might just continue experimenting on my fork (https://github.com/Bash-09/citro3d-rs) and seeing if I get anywhere.

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

No branches or pull requests

3 participants