258 259 if (ccinfo != NULL) { 260 // store glyph image in texture cell 261 j2d_glTexSubImage2D(GL_TEXTURE_2D, 0, 262 ccinfo->x, ccinfo->y, 263 glyph->width, glyph->height, 264 pixelFormat, GL_UNSIGNED_BYTE, glyph->image); 265 } 266 } 267 268 /** 269 * This is the GLSL fragment shader source code for rendering LCD-optimized 270 * text. Do not be frightened; it is much easier to understand than the 271 * equivalent ASM-like fragment program! 272 * 273 * The "uniform" variables at the top are initialized once the program is 274 * linked, and are updated at runtime as needed (e.g. when the source color 275 * changes, we will modify the "src_adj" value in OGLTR_UpdateLCDTextColor()). 276 * 277 * The "main" function is executed for each "fragment" (or pixel) in the 278 * glyph image. We have determined that the pow() function can be quite 279 * slow and it only operates on scalar values, not vectors as we require. 280 * So instead we build two 3D textures containing gamma (and inverse gamma) 281 * lookup tables that allow us to approximate a component-wise pow() function 282 * with a single 3D texture lookup. This approach is at least 2x faster 283 * than the equivalent pow() calls. 284 * 285 * The variables involved in the equation can be expressed as follows: 286 * 287 * Cs = Color component of the source (foreground color) [0.0, 1.0] 288 * Cd = Color component of the destination (background color) [0.0, 1.0] 289 * Cr = Color component to be written to the destination [0.0, 1.0] 290 * Ag = Glyph alpha (aka intensity or coverage) [0.0, 1.0] 291 * Ga = Gamma adjustment in the range [1.0, 2.5] 292 * (^ means raised to the power) 293 * 294 * And here is the theoretical equation approximated by this shader: 295 * 296 * Cr = (Ag*(Cs^Ga) + (1-Ag)*(Cd^Ga)) ^ (1/Ga) 297 */ 298 static const char *lcdTextShaderSource = 299 "uniform vec3 src_adj;" 300 "uniform sampler2D glyph_tex;" 301 "uniform sampler2D dst_tex;" 302 "uniform sampler3D invgamma_tex;" 303 "uniform sampler3D gamma_tex;" 304 "" 305 "void main(void)" 306 "{" 307 // load the RGB value from the glyph image at the current texcoord 308 " vec3 glyph_clr = vec3(texture2D(glyph_tex, gl_TexCoord[0].st));" 309 " if (glyph_clr == vec3(0.0)) {" 310 // zero coverage, so skip this fragment 311 " discard;" 312 " }" 313 // load the RGB value from the corresponding destination pixel 314 " vec3 dst_clr = vec3(texture2D(dst_tex, gl_TexCoord[1].st));" 315 // gamma adjust the dest color using the invgamma LUT 316 " vec3 dst_adj = vec3(texture3D(invgamma_tex, dst_clr.stp));" 317 // linearly interpolate the three color values 318 " vec3 result = mix(dst_adj, src_adj, glyph_clr);" 319 // gamma re-adjust the resulting color (alpha is always set to 1.0) 320 " gl_FragColor = vec4(vec3(texture3D(gamma_tex, result.stp)), 1.0);" 321 "}"; 322 323 /** 324 * Compiles and links the LCD text shader program. If successful, this 325 * function returns a handle to the newly created shader program; otherwise 326 * returns 0. 327 */ 328 static GLhandleARB 329 OGLTR_CreateLCDTextProgram() 330 { 331 GLhandleARB lcdTextProgram; 332 GLint loc; 333 334 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateLCDTextProgram"); 335 336 lcdTextProgram = OGLContext_CreateFragmentProgram(lcdTextShaderSource); 337 if (lcdTextProgram == 0) { 338 J2dRlsTraceLn(J2D_TRACE_ERROR, 339 "OGLTR_CreateLCDTextProgram: error creating program"); 340 return 0; 341 } 342 343 // "use" the program object temporarily so that we can set the uniforms 344 j2d_glUseProgramObjectARB(lcdTextProgram); 345 346 // set the "uniform" values 347 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "glyph_tex"); 348 j2d_glUniform1iARB(loc, 0); // texture unit 0 349 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "dst_tex"); 350 j2d_glUniform1iARB(loc, 1); // texture unit 1 351 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "invgamma_tex"); 352 j2d_glUniform1iARB(loc, 2); // texture unit 2 353 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "gamma_tex"); 354 j2d_glUniform1iARB(loc, 3); // texture unit 3 355 356 // "unuse" the program object; it will be re-bound later as needed 357 j2d_glUseProgramObjectARB(0); 358 359 return lcdTextProgram; 360 } 361 362 /** 363 * Initializes a 3D texture object for use as a three-dimensional gamma 364 * lookup table. Note that the wrap mode is initialized to GL_LINEAR so 365 * that the table will interpolate adjacent values when the index falls 366 * somewhere in between. 367 */ 368 static GLuint 369 OGLTR_InitGammaLutTexture() 370 { 371 GLuint lutTextureID; 372 373 j2d_glGenTextures(1, &lutTextureID); 374 j2d_glBindTexture(GL_TEXTURE_3D, lutTextureID); 375 j2d_glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 376 j2d_glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 377 j2d_glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 378 j2d_glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 379 j2d_glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 380 381 return lutTextureID; 382 } 383 384 /** 385 * Updates the lookup table in the given texture object with the float 386 * values in the given system memory buffer. Note that we could use 387 * glTexSubImage3D() when updating the texture after its first 388 * initialization, but since we're updating the entire table (with 389 * power-of-two dimensions) and this is a relatively rare event, we'll 390 * just stick with glTexImage3D(). 391 */ 392 static void 393 OGLTR_UpdateGammaLutTexture(GLuint texID, GLfloat *lut, jint size) 394 { 395 j2d_glBindTexture(GL_TEXTURE_3D, texID); 396 j2d_glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB8, 397 size, size, size, 0, GL_RGB, GL_FLOAT, lut); 398 } 399 400 /** 401 * (Re)Initializes the gamma lookup table textures. 402 * 403 * The given contrast value is an int in the range [100, 250] which we will 404 * then scale to fit in the range [1.0, 2.5]. We create two LUTs, one 405 * that essentially calculates pow(x, gamma) and the other calculates 406 * pow(x, 1/gamma). These values are replicated in all three dimensions, so 407 * given a single 3D texture coordinate (typically this will be a triplet 408 * in the form (r,g,b)), the 3D texture lookup will return an RGB triplet: 409 * 410 * (pow(r,g), pow(y,g), pow(z,g) 411 * 412 * where g is either gamma or 1/gamma, depending on the table. 413 */ 414 static jboolean 415 OGLTR_UpdateLCDTextContrast(jint contrast) 416 { 417 double gamma = ((double)contrast) / 100.0; 418 double ig = gamma; 419 double g = 1.0 / ig; 420 GLfloat lut[LUT_EDGE][LUT_EDGE][LUT_EDGE][3]; 421 GLfloat invlut[LUT_EDGE][LUT_EDGE][LUT_EDGE][3]; 422 int min = 0; 423 int max = LUT_EDGE - 1; 424 int x, y, z; 425 426 J2dTraceLn1(J2D_TRACE_INFO, 427 "OGLTR_UpdateLCDTextContrast: contrast=%d", contrast); 428 429 for (z = min; z <= max; z++) { 430 double zval = ((double)z) / max; 431 GLfloat gz = (GLfloat)pow(zval, g); 432 GLfloat igz = (GLfloat)pow(zval, ig); 433 434 for (y = min; y <= max; y++) { 435 double yval = ((double)y) / max; 436 GLfloat gy = (GLfloat)pow(yval, g); 437 GLfloat igy = (GLfloat)pow(yval, ig); 438 439 for (x = min; x <= max; x++) { 440 double xval = ((double)x) / max; 441 GLfloat gx = (GLfloat)pow(xval, g); 442 GLfloat igx = (GLfloat)pow(xval, ig); 443 444 lut[z][y][x][0] = gx; 445 lut[z][y][x][1] = gy; 446 lut[z][y][x][2] = gz; 447 448 invlut[z][y][x][0] = igx; 449 invlut[z][y][x][1] = igy; 450 invlut[z][y][x][2] = igz; 451 } 452 } 453 } 454 455 if (gammaLutTextureID == 0) { 456 gammaLutTextureID = OGLTR_InitGammaLutTexture(); 457 } 458 OGLTR_UpdateGammaLutTexture(gammaLutTextureID, (GLfloat *)lut, LUT_EDGE); 459 460 if (invGammaLutTextureID == 0) { 461 invGammaLutTextureID = OGLTR_InitGammaLutTexture(); 462 } 463 OGLTR_UpdateGammaLutTexture(invGammaLutTextureID, 464 (GLfloat *)invlut, LUT_EDGE); 465 466 return JNI_TRUE; 467 } 468 469 /** 470 * Updates the current gamma-adjusted source color ("src_adj") of the LCD 471 * text shader program. Note that we could calculate this value in the 472 * shader (e.g. just as we do for "dst_adj"), but would be unnecessary work 473 * (and a measurable performance hit, maybe around 5%) since this value is 474 * constant over the entire glyph list. So instead we just calculate the 475 * gamma-adjusted value once and update the uniform parameter of the LCD 476 * shader as needed. 477 */ 478 static jboolean 479 OGLTR_UpdateLCDTextColor(jint contrast) 480 { 481 double gamma = ((double)contrast) / 100.0; 482 GLfloat radj, gadj, badj; 483 GLfloat clr[4]; 484 GLint loc; | 258 259 if (ccinfo != NULL) { 260 // store glyph image in texture cell 261 j2d_glTexSubImage2D(GL_TEXTURE_2D, 0, 262 ccinfo->x, ccinfo->y, 263 glyph->width, glyph->height, 264 pixelFormat, GL_UNSIGNED_BYTE, glyph->image); 265 } 266 } 267 268 /** 269 * This is the GLSL fragment shader source code for rendering LCD-optimized 270 * text. Do not be frightened; it is much easier to understand than the 271 * equivalent ASM-like fragment program! 272 * 273 * The "uniform" variables at the top are initialized once the program is 274 * linked, and are updated at runtime as needed (e.g. when the source color 275 * changes, we will modify the "src_adj" value in OGLTR_UpdateLCDTextColor()). 276 * 277 * The "main" function is executed for each "fragment" (or pixel) in the 278 * glyph image. The pow() routine operates on vectors, gives precise results, 279 * and provides acceptable level of performance, so we use it to perform 280 * the gamma adjustment. 281 * 282 * The variables involved in the equation can be expressed as follows: 283 * 284 * Cs = Color component of the source (foreground color) [0.0, 1.0] 285 * Cd = Color component of the destination (background color) [0.0, 1.0] 286 * Cr = Color component to be written to the destination [0.0, 1.0] 287 * Ag = Glyph alpha (aka intensity or coverage) [0.0, 1.0] 288 * Ga = Gamma adjustment in the range [1.0, 2.5] 289 * (^ means raised to the power) 290 * 291 * And here is the theoretical equation approximated by this shader: 292 * 293 * Cr = (Ag*(Cs^Ga) + (1-Ag)*(Cd^Ga)) ^ (1/Ga) 294 */ 295 static const char *lcdTextShaderSource = 296 "uniform vec3 src_adj;" 297 "uniform sampler2D glyph_tex;" 298 "uniform sampler2D dst_tex;" 299 "uniform vec3 gamma;" 300 "uniform vec3 invgamma;" 301 "" 302 "void main(void)" 303 "{" 304 // load the RGB value from the glyph image at the current texcoord 305 " vec3 glyph_clr = vec3(texture2D(glyph_tex, gl_TexCoord[0].st));" 306 " if (glyph_clr == vec3(0.0)) {" 307 // zero coverage, so skip this fragment 308 " discard;" 309 " }" 310 // load the RGB value from the corresponding destination pixel 311 " vec3 dst_clr = vec3(texture2D(dst_tex, gl_TexCoord[1].st));" 312 // gamma adjust the dest color 313 " vec3 dst_adj = pow(dst_clr.rgb, gamma);" 314 // linearly interpolate the three color values 315 " vec3 result = mix(dst_adj, src_adj, glyph_clr);" 316 // gamma re-adjust the resulting color (alpha is always set to 1.0) 317 " gl_FragColor = vec4(pow(result.rgb, invgamma), 1.0);" 318 "}"; 319 320 /** 321 * Compiles and links the LCD text shader program. If successful, this 322 * function returns a handle to the newly created shader program; otherwise 323 * returns 0. 324 */ 325 static GLhandleARB 326 OGLTR_CreateLCDTextProgram() 327 { 328 GLhandleARB lcdTextProgram; 329 GLint loc; 330 331 J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateLCDTextProgram"); 332 333 lcdTextProgram = OGLContext_CreateFragmentProgram(lcdTextShaderSource); 334 if (lcdTextProgram == 0) { 335 J2dRlsTraceLn(J2D_TRACE_ERROR, 336 "OGLTR_CreateLCDTextProgram: error creating program"); 337 return 0; 338 } 339 340 // "use" the program object temporarily so that we can set the uniforms 341 j2d_glUseProgramObjectARB(lcdTextProgram); 342 343 // set the "uniform" values 344 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "glyph_tex"); 345 j2d_glUniform1iARB(loc, 0); // texture unit 0 346 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "dst_tex"); 347 j2d_glUniform1iARB(loc, 1); // texture unit 1 348 349 // "unuse" the program object; it will be re-bound later as needed 350 j2d_glUseProgramObjectARB(0); 351 352 return lcdTextProgram; 353 } 354 355 /** 356 * (Re)Initializes the gamma related uniforms. 357 * 358 * The given contrast value is an int in the range [100, 250] which we will 359 * then scale to fit in the range [1.0, 2.5]. 360 */ 361 static jboolean 362 OGLTR_UpdateLCDTextContrast(jint contrast) 363 { 364 double g = ((double)contrast) / 100.0; 365 double ig = 1.0 / g; 366 GLint loc; 367 368 J2dTraceLn1(J2D_TRACE_INFO, 369 "OGLTR_UpdateLCDTextContrast: contrast=%d", contrast); 370 371 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "gamma"); 372 j2d_glUniform3fARB(loc, g, g, g); 373 374 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "invgamma"); 375 j2d_glUniform3fARB(loc, ig, ig, ig); 376 377 return JNI_TRUE; 378 } 379 380 /** 381 * Updates the current gamma-adjusted source color ("src_adj") of the LCD 382 * text shader program. Note that we could calculate this value in the 383 * shader (e.g. just as we do for "dst_adj"), but would be unnecessary work 384 * (and a measurable performance hit, maybe around 5%) since this value is 385 * constant over the entire glyph list. So instead we just calculate the 386 * gamma-adjusted value once and update the uniform parameter of the LCD 387 * shader as needed. 388 */ 389 static jboolean 390 OGLTR_UpdateLCDTextColor(jint contrast) 391 { 392 double gamma = ((double)contrast) / 100.0; 393 GLfloat radj, gadj, badj; 394 GLfloat clr[4]; 395 GLint loc; |