1

I'm trying to use NPOT-sized PNG images as textures in OpenGL ES 1.1 (so no GL_arb_texture_rectangle) using libpng 1.5. With SDL, I could just blit the NPOT image onto a NPOT texture, but I can't figure out how to do something similar with libpng.

When I'm using the original texture (1280x720), all I get is a white surface. When I resize it to 1024x512 on the file system, it displays fine.

For some reason, the NPOT texture works on a 4.2 AVD with -gpu on.

Here's the code. It's pretty much this, with some changes to read from an APK using libzip instead of fopening directly.

void png_zip_read(png_structp png, png_bytep data, png_size_t size)
{
    zip_file* file = static_cast<zip_file*>(png_get_io_ptr(png));
    zip_fread(file, data, size);
}

GLuint load_png_texture(const std::string& path, unsigned int& width,
                        unsigned int& height)
{
    if (!apk_path.length())
        throw "APK Path not set";

    zip* apk = zip_open(apk_path.c_str(), 0, NULL);
    if (!apk)
        throw "Error loading APK";

    zip_file* file = zip_fopen(apk, path.c_str(), 0);
    if (file == 0)
        throw path + ": " + strerror(errno);

    png_byte header[8];
    zip_fread(file, header, 8);
    if (png_sig_cmp(header, 0, 8)) {
        zip_fclose(file);
        zip_close(apk);
        throw path + " is not a PNG";
    }

    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                             NULL, NULL, NULL);
    if (!png) {
        zip_fclose(file);
        zip_close(apk);
        throw "Failed to create png struct";
    }

    png_infop info = png_create_info_struct(png);
    if (!info) {
        png_destroy_read_struct(&png, (png_infopp) NULL, (png_infopp) NULL);
        zip_fclose(file);
        zip_close(apk);
        throw "Failed to create png info struct";
    }

    png_infop info_end = png_create_info_struct(png);
    if (!info_end) {
        png_destroy_read_struct(&png, &info, (png_infopp) NULL);
        zip_fclose(file);
        zip_close(apk);
        throw "Failed to create png info struct";
    }

    if (setjmp(png_jmpbuf(png))) {
        png_destroy_read_struct(&png, &info, &info_end);
        zip_fclose(file);
        zip_close(apk);
        throw "Error from libpng";
    }

    png_set_read_fn(png, file, png_zip_read);
    png_set_sig_bytes(png, 8);
    png_read_info(png, info);

    int bit_depth, color_type;
    unsigned int temp_width, temp_height;
    png_get_IHDR(png, info, &temp_width, &temp_height, &bit_depth,
                 &color_type, NULL, NULL, NULL);
    width = temp_width;
    height = temp_height;

    png_read_update_info(png, info);

    int row_bytes = png_get_rowbytes(png, info);
    png_byte* image_data = new png_byte[row_bytes * height];
    if (!image_data) {
        png_destroy_read_struct(&png, &info, &info_end);
        zip_fclose(file);
        zip_close(apk);
        throw "Could not allocate memory for PNG image data";
    }

    png_bytep* row_pointers = new png_bytep[height];
    if (!row_pointers) {
        png_destroy_read_struct(&png, &info, &info_end);
        delete[] image_data;
        zip_fclose(file);
        zip_close(apk);
        throw "Could not allocate memory for PNG row pointers";
    }

    for (int i = 0; i < height; i++)
        row_pointers[height - 1 - i] = image_data + i * row_bytes;

    png_read_image(png, row_pointers);

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    GLenum format = GL_RGBA;
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
                 format, GL_UNSIGNED_BYTE, image_data);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    png_destroy_read_struct(&png, &info, &info_end);
    delete[] image_data;
    delete[] row_pointers;
    zip_fclose(file);
    zip_close(apk);
    return texture;
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
futlib
  • 8,258
  • 13
  • 40
  • 55

2 Answers2

2

After some more trial and error, I went back to frantic googling and finally found a code sample that does exactly what I'm looking for.

The secret is basically to create the OpenGL texture like this:

int texture_width = next_power_of_two(width);
int texture_height = next_power_of_two(height);

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

if (texture_width != width || texture_height != height) {
    glTexImage2D(GL_TEXTURE_2D, 0, format, texture_width,
                 texture_height, 0, format, GL_UNSIGNED_BYTE, NULL);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format,
                    GL_UNSIGNED_BYTE, image_data);
} else
    glTexImage2D(GL_TEXTURE_2D, 0, format, texture_width,
                 texture_height, 0, format, GL_UNSIGNED_BYTE,
                 image_data);

To render the image at its original size, the x/y texture coordinates have to be calculated as follows (instead of just using 1):

float x_coordinate = (width - 0.5f) / texture_width;
float y_coordinate = (height - 0.5f) / texture_height;
futlib
  • 8,258
  • 13
  • 40
  • 55
1

ES 1.1 officially does not support NPOT textures without extensions. It might accidentally work on some implementations, but that's just "luck".

The usual approach is to pack your NPOT images in larger POT textures and adjust texture coordinates accordingly. You can do this programmatically at runtime ("packing"), or just do it in the toolchain (most likely your art program).

Paul-Jan
  • 16,746
  • 1
  • 63
  • 95
  • Could you give me a hint on how to do this packing with libpng? I don't really know where to start. Can I just push transparent pixels into image_data? Or would you recommend I just create a single sprite sheet (or texture atlas, the same thing AFAIK) and save that as a single POT image? – futlib Jan 04 '13 at 10:49
  • Yes, I'd recommend doing it with the art tool or an existing texture packer like http://www.codeandweb.com/texturepacker rather than doing it in the code. You can always run your own stuff later. And yes, "sprite sheet" and "texture atlas" are the same concepts. – Paul-Jan Jan 04 '13 at 10:55
  • I've read some more about the texture atlas thing. Seems like I won't be able to use a single atlas due to hardware constraints, so I think I'd rather stick to one image per texture until performance issues come up. Do you happen to know how I could programmatically package the image at runtime using libpng? – futlib Jan 04 '13 at 14:51
  • You practically don't use libpng for that. Libpng gives you raw 32bits RGBA data. You just copy that (row by row, using right offset) into the corner of a slightly bigger (POTxPOT) blob of data. You then upload that using glTexImage2D, and correct your texture coordinates (1 becomes NPOTsize/POTsize). – Paul-Jan Jan 04 '13 at 15:06