/* * z64 * * Copyright (C) 2007 ziggy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * **/ #include "rdp.h" #include "rgl.h" #include const char *rdpImageFormats[] = { "RGBA", "YUV", "CI", "IA", "I", "???", "???", "???" }; rdpState_t rdpState; uint32_t rdpChanged; //rdpColor_t rdpTlut[1024]; uint8_t rdpTmem[4*0x1000]; int rdpFbFormat; int rdpFbSize; int rdpFbWidth; uint32_t rdpFbAddress; uint32_t rdpZbAddress; int rdpTiFormat; int rdpTiSize; int rdpTiWidth; uint32_t rdpTiAddress; rdpTile_t rdpTiles[8]; int rdpTileSet; struct area_t { int start, stop; uint32_t from; int fromLine, fromFormat, fromSize; }; #define MAX_TMEM_AREAS 16 static area_t tmemAreas[MAX_TMEM_AREAS]; static int nbTmemAreas; #ifdef RDP_DEBUG int rdp_dump; #endif #define MAXCMD 0x100000 static uint32_t rdp_cmd_data[MAXCMD+44]; static volatile int rdp_cmd_ptr = 0; static volatile int rdp_cmd_cur = 0; static int rdp_cmd_left = 0; #ifdef RDP_DEBUG uint32_t rdpTraceBuf[0x100000]; int rdpTracePos; #endif static void MarkTmemArea(int start, int stop, uint32_t from, uint32_t fromLine, int fromFormat, int fromSize) { int i; // remove areas that intersect for (i=0; istart) { memmove(tmemAreas+i, tmemAreas+i+1, nbTmemAreas-i-1); nbTmemAreas--; } DUMP("marking tmem %x --> %x rdram %x\n", start, stop, from); // add new area //rglAssert(nbTmemAreas < MAX_TMEM_AREAS); if (nbTmemAreas == MAX_TMEM_AREAS) { LOG("tmem areas buffer full, clearing\n"); nbTmemAreas = 0; } tmemAreas[nbTmemAreas].start = start; tmemAreas[nbTmemAreas].stop = stop; tmemAreas[nbTmemAreas].from = from; tmemAreas[nbTmemAreas].fromLine = fromLine; tmemAreas[nbTmemAreas].fromFormat = fromFormat; tmemAreas[nbTmemAreas].fromSize = fromSize; nbTmemAreas++; } uint32_t rdpGetTmemOrigin(int tmem, int * line, int * stop, int * format, int * size) { int i; for (i=0; i> 24) & 0x3f, w1, w2); } static void rdp_noop(uint32_t w1, uint32_t w2) { } static void triangle(uint32_t w1, uint32_t w2, int shade, int texture, int zbuffer) { rglTriangle(w1, w2, shade, texture, zbuffer, rdp_cmd_data + rdp_cmd_cur); } static void rdp_tri_noshade(uint32_t w1, uint32_t w2) { triangle(w1, w2, 0, 0, 0); } static void rdp_tri_noshade_z(uint32_t w1, uint32_t w2) { triangle(w1, w2, 0, 0, 1); } static void rdp_tri_tex(uint32_t w1, uint32_t w2) { triangle(w1, w2, 0, 1, 0); } static void rdp_tri_tex_z(uint32_t w1, uint32_t w2) { triangle(w1, w2, 0, 1, 1); } static void rdp_tri_shade(uint32_t w1, uint32_t w2) { triangle(w1, w2, 1, 0, 0); } static void rdp_tri_shade_z(uint32_t w1, uint32_t w2) { triangle(w1, w2, 1, 0, 1); } static void rdp_tri_texshade(uint32_t w1, uint32_t w2) { triangle(w1, w2, 1, 1, 0); } static void rdp_tri_texshade_z(uint32_t w1, uint32_t w2) { triangle(w1, w2, 1, 1, 1); } static void rdp_tex_rect(uint32_t w1, uint32_t w2) { uint32_t w3, w4; rdpTexRect_t rect; w3 = rdp_cmd_data[rdp_cmd_cur+2]; w4 = rdp_cmd_data[rdp_cmd_cur+3]; rect.tilenum = (w2 >> 24) & 0x7; rect.xl = (w1 >> 12) & 0xfff; rect.yl = (w1 >> 0) & 0xfff; rect.xh = (w2 >> 12) & 0xfff; rect.yh = (w2 >> 0) & 0xfff; rect.s = (w3 >> 16) & 0xffff; rect.t = (w3 >> 0) & 0xffff; rect.dsdx = (w4 >> 16) & 0xffff; rect.dtdy = (w4 >> 0) & 0xffff; rglTextureRectangle(&rect, 0); } static void rdp_tex_rect_flip(uint32_t w1, uint32_t w2) { uint32_t w3, w4; rdpTexRect_t rect; w3 = rdp_cmd_data[rdp_cmd_cur+2]; w4 = rdp_cmd_data[rdp_cmd_cur+3]; rect.tilenum = (w2 >> 24) & 0x7; rect.xl = (w1 >> 12) & 0xfff; rect.yl = (w1 >> 0) & 0xfff; rect.xh = (w2 >> 12) & 0xfff; rect.yh = (w2 >> 0) & 0xfff; rect.t = (w3 >> 16) & 0xffff; rect.s = (w3 >> 0) & 0xffff; rect.dtdy = (w4 >> 16) & 0xffff; rect.dsdx = (w4 >> 0) & 0xffff; rglTextureRectangle(&rect, 1); } static void rdp_sync_load(uint32_t w1, uint32_t w2) { // Nothing to do? } static void rdp_sync_pipe(uint32_t w1, uint32_t w2) { // Nothing to do? } static void rdp_sync_tile(uint32_t w1, uint32_t w2) { // Nothing to do? } void rdpSignalFullSync(); void rdpWaitFullSync(); #ifdef RDP_DEBUG int nbFullSync; #endif static void rdp_sync_full(uint32_t w1, uint32_t w2) { //printf("full sync\n"); rglFullSync(); rglUpdate(); if (rglSettings.async) rdpSignalFullSync(); else { *gfx.MI_INTR_REG |= 0x20; gfx.CheckInterrupts(); } #ifdef RDP_DEBUG nbFullSync++; #endif } static void rdp_set_key_gb(uint32_t w1, uint32_t w2) { //osd_die("RDP: unhandled command set_key_gb, %08X %08X\n", w1, w2); } static void rdp_set_key_r(uint32_t w1, uint32_t w2) { //osd_die("RDP: unhandled command set_key_r, %08X %08X\n", w1, w2); } static void rdp_set_convert(uint32_t w1, uint32_t w2) { rdpState.k5 = w2&0xff; //osd_die("RDP: unhandled command set_convert, %08X %08X\n", w1, w2); } static void rdp_set_scissor(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_CLIP; rdpState.clipMode = (w2 >> 24) & 3; rdpState.clip.xh = (w1 >> 12) & 0xfff; rdpState.clip.yh = (w1 >> 0) & 0xfff; rdpState.clip.xl = (w2 >> 12) & 0xfff; rdpState.clip.yl = (w2 >> 0) & 0xfff; // TODO: handle f & o? } static void rdp_set_prim_depth(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_MISC; rdpState.primitiveZ = (uint16_t)(w2 >> 16); rdpState.primitiveDeltaZ = (uint16_t)(w1); } static void rdp_set_other_modes(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_OTHER_MODES; rdpState.otherModes.w1 = w1; rdpState.otherModes.w2 = w2; } static void rdp_load_tlut(uint32_t w1, uint32_t w2) { int tilenum = (w2 >> 24) & 0x7; rdpChanged |= RDP_BITS_TILE_SETTINGS; #define tile rdpTiles[tilenum] //rdpTile_t tile; tile.sl = (w1 >> 12) & 0xfff; tile.tl = (w1 >> 0) & 0xfff; tile.sh = (w2 >> 12) & 0xfff; tile.th = (w2 >> 0) & 0xfff; int i; rdpChanged |= RDP_BITS_TLUT; int count = ((tile.sh - tile.sl + 4) >>2) * ((tile.th - tile.tl + 4) >>2); switch (rdpTiSize) { case RDP_PIXEL_SIZE_16BIT: { uint16_t *src = (uint16_t *)&rdram[(rdpTiAddress + (tile.tl >>2) * rdpTiWidth * 2 + ((tile.sl >>2) << rdpTiSize >> 1))/4]; uint16_t *dst = (uint16_t *)(rdpTmem + rdpTiles[tilenum].tmem); // printf("loading TLUT from %x --> %x\n", // tile.th * rdpTiWidth / 2 + (tile.sh << rdpTiSize >> 1)/4 for (i=0; i < count; i++) { dst[i*4] = src[i^1]; } break; } default: LOGERROR("RDP: load_tlut: size = %d\n", rdpTiSize); } #undef tile } static void rdp_set_tile_size(uint32_t w1, uint32_t w2) { int tilenum = (w2 >> 24) & 0x7; rdpChanged |= RDP_BITS_TILE_SETTINGS; #define tile rdpTiles[tilenum] tile.sl = (w1 >> 12) & 0xfff; tile.tl = (w1 >> 0) & 0xfff; tile.sh = (w2 >> 12) & 0xfff; tile.th = (w2 >> 0) & 0xfff; #undef tile } static void rdp_load_block(uint32_t w1, uint32_t w2) { int i, width; uint16_t sl, sh, tl, dxt; int tilenum = (w2 >> 24) & 0x7; uint32_t *src, *tc; int tb; rdpChanged |= RDP_BITS_TMEM; sl = ((w1 >> 12) & 0xfff); tl = ((w1 >> 0) & 0xfff) << 11; sh = ((w2 >> 12) & 0xfff); dxt = ((w2 >> 0) & 0xfff); width = (sh - sl + 1) << rdpTiSize >> 1; src = (uint32_t*)&rdram[0]; tc = (uint32_t*)rdpTmem; tb = rdpTiles[tilenum].tmem/4; //printf("Load block to %x width %x\n", rdpTiles[tilenum].tmem, width); MarkTmemArea(rdpTiles[tilenum].tmem, rdpTiles[tilenum].tmem + width, tl * rdpTiWidth*4 + rdpTiAddress + sl*4, 0, ~0, ~0); if (tb+width/4 > 0x1000/4) { LOG("load_block : fixup too large width\n"); width = 0x1000-tb*4; } if (dxt != 0) { int j=0; //rglAssert(tb+width/4 <= 0x1000/4); int swap = rdpTiles[tilenum].size == 3? 2 : 1; for (i=0; i < width / 4; i+=2) { int t = j >> 11; tc[(((tb+i) + 0) ^ ((t & 1) ? swap : 0))&0x3ff] = src[rdpTiAddress / 4 + ((tl * rdpTiWidth) / 4) + sl + i + 0]; tc[(((tb+i) + 1) ^ ((t & 1) ? swap : 0))&0x3ff] = src[rdpTiAddress / 4 + ((tl * rdpTiWidth) / 4) + sl + i + 1]; j += dxt; } } else { //rglAssert(tb+width/4 <= 0x1000/4); for (i=0; i < width / 4; i++) { tc[(tb+i)&0x3ff] = src[((tl * rdpTiWidth) / 4) + rdpTiAddress / 4 + sl + i]; } } } static void rdp_load_tile(uint32_t w1, uint32_t w2) { int i, j; uint16_t sl, sh, tl, th; int width, height; int tilenum = (w2 >> 24) & 0x7; int line; rdpChanged |= RDP_BITS_TMEM; sl = ((w1 >> 12) & 0xfff) / 4; tl = ((w1 >> 0) & 0xfff) / 4; sh = ((w2 >> 12) & 0xfff) / 4; th = ((w2 >> 0) & 0xfff) / 4; width = (sh - sl) + 1; height = (th - tl) + 1; // printf("Load tile to %x line %x height %d\n", // rdpTiles[tilenum].tmem, // rdpTiles[tilenum].line, // height); rdpTiles[tilenum].size = rdpTiSize; // CHECK THIS line = rdpTiles[tilenum].line; switch (rdpTiles[tilenum].size /*rdpTiSize*/) { case RDP_PIXEL_SIZE_8BIT: { uint8_t *src = (uint8_t*)&rdram[0]; uint8_t *tc = (uint8_t*)rdpTmem; int tb = rdpTiles[tilenum].tmem; MarkTmemArea(tb, tb + height*line, rdpTiAddress + tl * rdpTiWidth + sl, rdpTiWidth, rdpTiFormat, rdpTiSize); if (tb + (line * (height-1) + width) > 4096) { LOGERROR("rdp_load_tile 8-bit: tmem %04X, width %d, height %d = %d\n", rdpTiles[tilenum].tmem, width, height, width*height); height = (4096-tb)/line; } for (j=0; j < height; j++) { int tline = tb + (rdpTiles[tilenum].line * j); int s = ((j + tl) * rdpTiWidth) + sl; for (i=0; i < width; i++) { tc[(((tline+i) ^ BYTE_ADDR_XOR) ^ ((j & 1) ? 4 : 0))&0xfff] = src[(rdpTiAddress + s++) ^ BYTE_ADDR_XOR]; } } break; } case RDP_PIXEL_SIZE_16BIT: { uint16_t *src = (uint16_t*)&rdram[0]; uint16_t *tc = (uint16_t*)rdpTmem; int tb = (rdpTiles[tilenum].tmem / 2); if (tb + (line/2 * (height-1) + width) > 2048) { LOGERROR("rdp_load_tile 16-bit: tmem %04X, width %d, height %d = %d\n", rdpTiles[tilenum].tmem, width, height, width*height); height = (2048 - tb) / (line/2); } MarkTmemArea(tb*2, tb*2 + height*line, rdpTiAddress + (tl * rdpTiWidth + sl)*2, rdpTiWidth*2, rdpTiFormat, rdpTiSize); for (j=0; j < height; j++) { int tline = tb + ((rdpTiles[tilenum].line / 2) * j); int s = ((j + tl) * rdpTiWidth) + sl; for (i=0; i < width; i++) { tc[(((tline+i) ^ WORD_ADDR_XOR) ^ ((j & 1) ? 2 : 0))&0x7ff] = src[(rdpTiAddress / 2 + s++) ^ WORD_ADDR_XOR]; } } break; } case RDP_PIXEL_SIZE_32BIT: { uint32_t *src = (uint32_t*)&rdram[0]; uint32_t *tc = (uint32_t*)rdpTmem; int tb = (rdpTiles[tilenum].tmem / 4); MarkTmemArea(tb*4, tb*4 + height*line*2, rdpTiAddress + (tl * rdpTiWidth + sl)*4, rdpTiWidth*4, rdpTiFormat, rdpTiSize); if (tb + (line/2 * (height-1) + width) > 1024) { rdp_log(M64MSG_ERROR, "rdp_load_tile 32-bit: tmem %04X, width %d, height %d = %d\n", rdpTiles[tilenum].tmem, width, height, width*height); } for (j=0; j < height; j++) { int tline = tb + ((rdpTiles[tilenum].line / 2) * j); int s = ((j + tl) * rdpTiWidth) + sl; for (i=0; i < width; i++) { tc[((tline+i) ^ ((j & 1) ? 2 : 0))&0x3ff] = src[(rdpTiAddress / 4 + s++)]; } } break; } default: rdp_log(M64MSG_ERROR, "RDP: load_tile: size = %d\n", rdpTiSize); } } static void rdp_set_tile(uint32_t w1, uint32_t w2) { int tilenum = (w2 >> 24) & 0x7; //int i; rdpChanged |= RDP_BITS_TILE_SETTINGS; rdpTileSet |= 1<> 21) & 0x7; tile.size = (w1 >> 19) & 0x3; tile.line = ((w1 >> 9) & 0x1ff) * 8; tile.tmem = ((w1 >> 0) & 0x1ff) * 8; tile.palette= (w2 >> 20) & 0xf; tile.ct = (w2 >> 19) & 0x1; tile.mt = (w2 >> 18) & 0x1; tile.mask_t = (w2 >> 14) & 0xf; tile.shift_t= (w2 >> 10) & 0xf; if (tile.shift_t >= 12) tile.shift_t -= 16; tile.cs = (w2 >> 9) & 0x1; tile.ms = (w2 >> 8) & 0x1; tile.mask_s = (w2 >> 4) & 0xf; tile.shift_s= (w2 >> 0) & 0xf; if (tile.shift_s >= 12) tile.shift_s -= 16; #undef tile } static void rdp_fill_rect(uint32_t w1, uint32_t w2) { rdpRect_t rect; rect.xl = (w1 >> 12) & 0xfff; rect.yl = (w1 >> 0) & 0xfff; rect.xh = (w2 >> 12) & 0xfff; rect.yh = (w2 >> 0) & 0xfff; rglFillRectangle(&rect); } static void rdp_set_fill_color(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_FILL_COLOR; rdpState.fillColor = w2; } static void rdp_set_fog_color(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_FOG_COLOR; rdpState.fogColor = w2; } static void rdp_set_blend_color(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_BLEND_COLOR; rdpState.blendColor = w2; } static void rdp_set_prim_color(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_PRIM_COLOR; // TODO: prim min level, prim_level rdpState.primColor = w2; } static void rdp_set_env_color(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_ENV_COLOR; rdpState.envColor = w2; } static void rdp_set_combine(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_COMBINE_MODES; rdpState.combineModes.w1 = w1; rdpState.combineModes.w2 = w2; } static void rdp_set_texture_image(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_TI_SETTINGS; rdpTiFormat = (w1 >> 21) & 0x7; rdpTiSize = (w1 >> 19) & 0x3; rdpTiWidth = (w1 & 0x3ff) + 1; rdpTiAddress = w2 & 0x01ffffff; } static void rdp_set_mask_image(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_ZB_SETTINGS; rdpZbAddress = w2 & 0x01ffffff; } static void rdp_set_color_image(uint32_t w1, uint32_t w2) { rdpChanged |= RDP_BITS_FB_SETTINGS; rdpFbFormat = (w1 >> 21) & 0x7; rdpFbSize = (w1 >> 19) & 0x3; rdpFbWidth = (w1 & 0x3ff) + 1; rdpFbAddress = w2 & 0x01ffffff; } /*****************************************************************************/ static void (* rdp_command_table[64])(uint32_t w1, uint32_t w2) = { /* 0x00 */ rdp_noop, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_tri_noshade, rdp_tri_noshade_z, rdp_tri_tex, rdp_tri_tex_z, rdp_tri_shade, rdp_tri_shade_z, rdp_tri_texshade, rdp_tri_texshade_z, /* 0x10 */ rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, /* 0x20 */ rdp_invalid, rdp_invalid, rdp_invalid, rdp_invalid, rdp_tex_rect, rdp_tex_rect_flip, rdp_sync_load, rdp_sync_pipe, rdp_sync_tile, rdp_sync_full, rdp_set_key_gb, rdp_set_key_r, rdp_set_convert, rdp_set_scissor, rdp_set_prim_depth, rdp_set_other_modes, /* 0x30 */ rdp_load_tlut, rdp_invalid, rdp_set_tile_size, rdp_load_block, rdp_load_tile, rdp_set_tile, rdp_fill_rect, rdp_set_fill_color, rdp_set_fog_color, rdp_set_blend_color, rdp_set_prim_color, rdp_set_env_color, rdp_set_combine, rdp_set_texture_image, rdp_set_mask_image, rdp_set_color_image }; void rdp_process_list(void) { //int i; uint32_t cmd;//, length, cmd_length; rglUpdateStatus(); if (!rglSettings.threaded) rdp_store_list(); if (rglStatus == RGL_STATUS_CLOSED) return; // this causes problem with depth writeback in zelda mm // but is necessary for in fisherman rglUpdate(); while (rdp_cmd_cur != rdp_cmd_ptr) { cmd = (rdp_cmd_data[rdp_cmd_cur] >> 24) & 0x3f; // if (((rdp_cmd_data[rdp_cmd_cur] >> 24) & 0xc0) != 0xc0) // { // LOGERROR("rdp_process_list: invalid rdp command %08X at %08X\n", rdp_cmd_data[rdp_cmd_cur], dp_start+(rdp_cmd_cur * 4)); // } if ((((rdp_cmd_ptr-rdp_cmd_cur)&(MAXCMD-1)) * 4) < rdp_command_length[cmd]) { // LOGERROR("rdp_process_list: not enough rdp command data: cur = %d, ptr = %d, expected = %d\n", rdp_cmd_cur, rdp_cmd_ptr, rdp_command_length[cmd]); // return; break; } #ifdef RDP_DEBUG if (rdp_dump) { char string[4000]; int rdp_dasm(uint32_t * rdp_cmd_data, int rdp_cmd_cur, int length, char *buffer); rdp_dasm(rdp_cmd_data, rdp_cmd_cur, rdp_command_length[cmd], string); fprintf(stderr, "%08X: %08X %08X %s\n", dp_start+(rdp_cmd_cur * 4), rdp_cmd_data[rdp_cmd_cur+0], rdp_cmd_data[rdp_cmd_cur+1], string); } #endif #ifdef RDP_DEBUG memcpy(rdpTraceBuf+rdpTracePos, rdp_cmd_data+rdp_cmd_cur, rdp_command_length[cmd]); #endif if (rdp_cmd_cur + rdp_command_length[cmd]/4 > MAXCMD) memcpy(rdp_cmd_data + MAXCMD, rdp_cmd_data, rdp_command_length[cmd] - (MAXCMD - rdp_cmd_cur)*4); // execute the command rdp_command_table[cmd](rdp_cmd_data[rdp_cmd_cur+0], rdp_cmd_data[rdp_cmd_cur+1]); #ifdef RDP_DEBUG rdpTracePos += rdp_command_length[cmd] / 4; rglAssert(rdpTracePos < sizeof(rdpTraceBuf)/sizeof(rdpTraceBuf[0])); #endif rdp_cmd_cur = (rdp_cmd_cur + rdp_command_length[cmd] / 4) & (MAXCMD-1); } // dp_current = dp_end; // dp_start = dp_end; dp_start = dp_current; dp_status &= ~0x0002; } int rdp_store_list(void) { uint32_t i; uint32_t data, cmd, length; int sync = 0; // while (dp_current < dp_end) { // } // dp_status &= ~0x0002; length = dp_end - dp_current; // LOG("rdp start %x cur %x end %x length %d dp_status %x\n", // dp_start, dp_current, dp_end, // length, dp_status); if (dp_end <= dp_current) { return 0; } // load command data for (i=0; i < length; i += 4) { data = READ_RDP_DATA(dp_current + i); if (rglSettings.async) { if (rdp_cmd_left) { rdp_cmd_left--; } else { cmd = (data >> 24) & 0x3f; rdp_cmd_left = rdp_command_length[cmd]/4-1; if (cmd == 0x29) // full_sync sync = 1; } } rdp_cmd_data[rdp_cmd_ptr] = data; rdp_cmd_ptr = (rdp_cmd_ptr + 1) & (MAXCMD-1); } dp_current += length; return sync; } int rdp_init() { rdp_cmd_cur = rdp_cmd_ptr = 0; rdp_cmd_left = 0; #ifdef RDP_DEBUG rdpTracePos = 0; #endif nbTmemAreas = 0; return rglInit(); }