src/texture.c

Mon, 21 Apr 2025 17:27:33 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 21 Apr 2025 17:27:33 +0200
changeset 89
e1f682a8a145
parent 88
6234b7ea48f3
permissions
-rw-r--r--

use refcounted objects for textures instead of pass-by-value int structs

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * Copyright 2024 Mike Becker. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "ascension/context.h"
#include "ascension/texture.h"
#include "ascension/error.h"
#include "ascension/filesystem.h"

#include <assert.h>
#include <cx/utils.h>
#include <GL/glew.h>
#include <SDL2/SDL_image.h>

void asc_texture_bind(AscTexture const *tex, int uniform_location, int unit) {
    glActiveTexture(GL_TEXTURE0 + unit);
    GLenum error = glGetError();
    if (error == GL_INVALID_ENUM) {
        asc_error("Tried to use more texture units than available.");
    }
    glBindTexture(tex->target, tex->tex_id);
    glUniform1i(uniform_location, unit);
    asc_error_catch_all_gl();
}

void asc_texture_from_surface(AscTexture *tex, SDL_Surface const *surface) {
    if (tex->tex_id == 0) {
        asc_error("Tried to use uninitialized texture.");
        asc_dprintf("Texture address: %"PRIxPTR, (uintptr_t) tex);
        return;
    }
    tex->width = surface->w;
    tex->height = surface->h;
    glBindTexture(tex->target,tex->tex_id);
    glPixelStorei(GL_UNPACK_ROW_LENGTH,
                  surface->pitch / surface->format->BytesPerPixel);

    // Determine the format and internal format based on the SDL surface format
    GLint internal_format;
    GLenum format;
    switch (surface->format->format) {
        case SDL_PIXELFORMAT_RGB24:
            internal_format = GL_RGB8;
            format = GL_RGB;
            break;
        case SDL_PIXELFORMAT_BGR24:
            internal_format = GL_RGB8;
            format = GL_BGR;
            break;
        case SDL_PIXELFORMAT_RGBA32:
        case SDL_PIXELFORMAT_ABGR32:
            internal_format = GL_RGBA8;
            format = GL_RGBA;
            break;
        case SDL_PIXELFORMAT_ARGB32:
        case SDL_PIXELFORMAT_BGRA32:
            internal_format = GL_RGBA8;
            format = GL_BGRA;
            break;
        default:
            // TODO: add more output once asc_error allows format strings
            asc_error("Unsupported pixel format.");
            return;
    }
    glTexImage2D(tex->target, 0, internal_format,
                 surface->w, surface->h,
                 0, format,
                 GL_UNSIGNED_BYTE, surface->pixels);
    // TODO: replace catch all with proper error handling for this single call
    asc_error_catch_all_gl();
}

void asc_texture_from_file(AscTexture *tex, const char *name) {
    cxmutstr filepath = asc_filesystem_combine_paths(cx_strcast(asc_context.texture_path), cx_str(name));
    asc_dprintf("Load texture from %" CX_PRIstr, CX_SFMT(filepath));
    SDL_Surface *image = IMG_Load(filepath.ptr);
    cx_strfree(&filepath);
    if (image == NULL) {
        asc_error("Failed to load texture.");
        asc_error(IMG_GetError());
        return;
    }
    asc_texture_from_surface(tex, image);
    asc_dprintf("Free temporary surface %"PRIxPTR, (uintptr_t) image);
    SDL_FreeSurface(image);
}

void asc_texture_init(
        AscTexture *tex,
        unsigned count,
        enum asc_texture_target target,
        enum asc_texture_min_filter min_filter,
        enum asc_texture_mag_filter mag_filter
) {
    static const GLenum texture_targets[] = {
            GL_TEXTURE_RECTANGLE,
            GL_TEXTURE_2D,
    };
    static const GLint texture_filters[] = {
            GL_NEAREST,
            GL_LINEAR,
            GL_NEAREST_MIPMAP_NEAREST,
            GL_LINEAR_MIPMAP_NEAREST,
            GL_NEAREST_MIPMAP_LINEAR,
            GL_LINEAR_MIPMAP_LINEAR
    };
    assert(target < sizeof(texture_targets) / sizeof(GLenum));
    assert(min_filter < sizeof(texture_filters) / sizeof(GLint));
    assert(mag_filter < 2); // mag filter only supports nearest/linear

    GLuint textures[count];
    glGenTextures(count, textures);

    for (unsigned i = 0; i < count; ++i) {
        memset(&tex[i], 0, sizeof(AscTexture));
        tex[i].tex_id = textures[i];
        tex[i].target = texture_targets[target];
        glBindTexture(tex[i].target, tex[i].tex_id);
        glTexParameteri(tex[i].target, GL_TEXTURE_MIN_FILTER,
                        texture_filters[min_filter]);
        glTexParameteri(tex[i].target, GL_TEXTURE_MAG_FILTER,
                        texture_filters[mag_filter]);
        asc_dprintf("Initialized texture: %u", tex[i].tex_id);
    }

    // TODO: proper error handling for each gl call
    asc_error_catch_all_gl();
}

void asc_texture_destroy(AscTexture *tex, unsigned count) {
    GLuint textures[count];
    for (unsigned i = 0; i < count; ++i) {
        if (tex[i].refcount > 0) {
            // TODO: asc_wprintf() for warnings
            asc_dprintf("Texture %u still in use by %u objects.",
                tex[i].tex_id, tex[i].refcount);
        }
        asc_dprintf("Destroy texture: %u", tex[i].tex_id);
        textures[i] = tex[i].tex_id;
        memset(&tex[i], 0, sizeof(AscTexture));
    }
    glDeleteTextures(count, textures);
}

mercurial