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; 485 486 J2dTraceLn1(J2D_TRACE_INFO, 487 "OGLTR_UpdateLCDTextColor: contrast=%d", contrast); 488 489 /* 490 * Note: Ideally we would update the "src_adj" uniform parameter only 491 * when there is a change in the source color. Fortunately, the cost 492 * of querying the current OpenGL color state and updating the uniform 493 * value is quite small, and in the common case we only need to do this 494 * once per GlyphList, so we gain little from trying to optimize too 495 * eagerly here. 496 */ 497 498 // get the current OpenGL primary color state 499 j2d_glGetFloatv(GL_CURRENT_COLOR, clr); 500 501 // gamma adjust the primary color 502 radj = (GLfloat)pow(clr[0], gamma); 503 gadj = (GLfloat)pow(clr[1], gamma); 504 badj = (GLfloat)pow(clr[2], gamma); 505 506 // update the "src_adj" parameter of the shader program with this value 507 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "src_adj"); 508 j2d_glUniform3fARB(loc, radj, gadj, badj); 509 510 return JNI_TRUE; 511 } 512 513 /** 514 * Enables the LCD text shader and updates any related state, such as the 515 * gamma lookup table textures. 516 */ 517 static jboolean 518 OGLTR_EnableLCDGlyphModeState(GLuint glyphTextureID, jint contrast) 519 { 520 // bind the texture containing glyph data to texture unit 0 521 j2d_glActiveTextureARB(GL_TEXTURE0_ARB); 522 j2d_glBindTexture(GL_TEXTURE_2D, glyphTextureID); 523 524 // bind the texture tile containing destination data to texture unit 1 | 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 vec3 gamma;" 303 "uniform vec3 invgamma;" 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 = pow(dst_clr.rgb, invgamma);" 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(pow(result.rgb, gamma), 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 352 // "unuse" the program object; it will be re-bound later as needed 353 j2d_glUseProgramObjectARB(0); 354 355 return lcdTextProgram; 356 } 357 358 /** 359 * (Re)Initializes the gamma related uniforms. 360 * 361 * The given contrast value is an int in the range [100, 250] which we will 362 * then scale to fit in the range [1.0, 2.5]. 363 */ 364 static jboolean 365 OGLTR_UpdateLCDTextContrast(jint contrast) 366 { 367 double gamma = ((double)contrast) / 100.0; 368 double ig = gamma; 369 double g = 1.0 / ig; 370 GLint loc; 371 372 J2dTraceLn1(J2D_TRACE_INFO, 373 "OGLTR_UpdateLCDTextContrast: contrast=%d", contrast); 374 375 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "gamma"); 376 j2d_glUniform3fARB(loc, g, g, g); 377 378 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "invgamma"); 379 j2d_glUniform3fARB(loc, ig, ig, ig); 380 381 return JNI_TRUE; 382 } 383 384 /** 385 * Updates the current gamma-adjusted source color ("src_adj") of the LCD 386 * text shader program. Note that we could calculate this value in the 387 * shader (e.g. just as we do for "dst_adj"), but would be unnecessary work 388 * (and a measurable performance hit, maybe around 5%) since this value is 389 * constant over the entire glyph list. So instead we just calculate the 390 * gamma-adjusted value once and update the uniform parameter of the LCD 391 * shader as needed. 392 */ 393 static jboolean 394 OGLTR_UpdateLCDTextColor(jint contrast) 395 { 396 double invgamma = ((double)contrast) / 100; 397 GLfloat radj, gadj, badj; 398 GLfloat clr[4]; 399 GLint loc; 400 401 J2dTraceLn1(J2D_TRACE_INFO, 402 "OGLTR_UpdateLCDTextColor: contrast=%d", contrast); 403 404 /* 405 * Note: Ideally we would update the "src_adj" uniform parameter only 406 * when there is a change in the source color. Fortunately, the cost 407 * of querying the current OpenGL color state and updating the uniform 408 * value is quite small, and in the common case we only need to do this 409 * once per GlyphList, so we gain little from trying to optimize too 410 * eagerly here. 411 */ 412 413 // get the current OpenGL primary color state 414 j2d_glGetFloatv(GL_CURRENT_COLOR, clr); 415 416 // gamma adjust the primary color 417 radj = (GLfloat)pow(clr[0], invgamma); 418 gadj = (GLfloat)pow(clr[1], invgamma); 419 badj = (GLfloat)pow(clr[2], invgamma); 420 421 // update the "src_adj" parameter of the shader program with this value 422 loc = j2d_glGetUniformLocationARB(lcdTextProgram, "src_adj"); 423 j2d_glUniform3fARB(loc, radj, gadj, badj); 424 425 return JNI_TRUE; 426 } 427 428 /** 429 * Enables the LCD text shader and updates any related state, such as the 430 * gamma lookup table textures. 431 */ 432 static jboolean 433 OGLTR_EnableLCDGlyphModeState(GLuint glyphTextureID, jint contrast) 434 { 435 // bind the texture containing glyph data to texture unit 0 436 j2d_glActiveTextureARB(GL_TEXTURE0_ARB); 437 j2d_glBindTexture(GL_TEXTURE_2D, glyphTextureID); 438 439 // bind the texture tile containing destination data to texture unit 1 |