Integrate VTK-style FXAA CPU post-processing
--------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: starseeker <238416+starseeker@users.noreply.github.com>
This commit is contained in:
co-authored by
copilot-swe-agent[bot]
parent
eb7b3ab04c
commit
af465ac40d
@@ -1,6 +1,8 @@
|
||||
This repository contains Mesa libraries. The examples directory, if present,
|
||||
contains additional 3rd party codes. Check each source code file for specific
|
||||
terms. The default Mesa license is:
|
||||
terms.
|
||||
|
||||
The default Mesa license is:
|
||||
|
||||
Mesa 3-D graphics library
|
||||
Version: 7.0
|
||||
@@ -55,3 +57,32 @@ shall not be used in advertising or otherwise to promote the sale, use or
|
||||
other dealings in this Software without prior written authorization from
|
||||
Silicon Graphics, Inc.
|
||||
|
||||
VTK's BSD license (applies to FXAA port in src/fxaa)
|
||||
|
||||
Copyright (c) 1993-2015 Ken Martin, Will Schroeder, Bill Lorensen
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* 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.
|
||||
|
||||
* Neither name of Ken Martin, Will Schroeder, or Bill Lorensen nor the names
|
||||
of any contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
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 AUTHORS 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.
|
||||
|
||||
@@ -33,7 +33,82 @@ include things like static analyzer clean-ups and updating header includes to
|
||||
avoid accidentally including system versions of gl.h.
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Fast Approximate Anti-Aliasing
|
||||
|
||||
VTK-style Fast Approximate Anti-Aliasing) has been integrated into OSMesa as an
|
||||
optional built-in post-processing feature. This integration provides automatic
|
||||
edge smoothing for rendered images with minimal performance impact.
|
||||
|
||||
## Features
|
||||
|
||||
- **Integrated API**: Enable/disable FXAA with a single function call
|
||||
- **sRGB Color Space**: Processes images in sRGB color space (matching VTK's approach) for improved visual quality
|
||||
- **Automatic Application**: FXAA is applied automatically when `glFinish()` is called
|
||||
- **VTK-Compatible**: Uses the same algorithm and color space handling as VTK's GPU-based FXAA
|
||||
|
||||
## API
|
||||
|
||||
### OSMesaFXAAEnable
|
||||
|
||||
```c
|
||||
void OSMesaFXAAEnable(GLboolean enable);
|
||||
```
|
||||
|
||||
Enable or disable FXAA post-processing for the current OSMesa context.
|
||||
|
||||
**Parameters:**
|
||||
- `enable`: `GL_TRUE` to enable FXAA, `GL_FALSE` to disable
|
||||
|
||||
**Example:**
|
||||
```c
|
||||
OSMesaContext ctx = OSMesaCreateContextExt(OSMESA_RGBA, 16, 0, 0, NULL);
|
||||
OSMesaMakeCurrent(ctx, buffer, GL_UNSIGNED_BYTE, width, height);
|
||||
|
||||
/* Enable FXAA */
|
||||
OSMesaFXAAEnable(GL_TRUE);
|
||||
|
||||
/* Render scene */
|
||||
render_scene();
|
||||
|
||||
/* FXAA is automatically applied when glFinish() is called */
|
||||
glFinish();
|
||||
```
|
||||
|
||||
## Color Space Handling
|
||||
|
||||
The implementation converts rendered images through the following pipeline:
|
||||
|
||||
1. **Linear RGB** (from OpenGL rendering)
|
||||
2. **→ sRGB** (via gamma encoding for FXAA processing)
|
||||
3. **FXAA processing** (in sRGB color space)
|
||||
4. **→ Linear RGB** (via gamma decoding for final output)
|
||||
|
||||
This matches VTK's approach and provides better visual quality than processing
|
||||
in linear space, as FXAA's luminance calculations work better in perceptual
|
||||
color space.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Currently only works with RGBA 8-bit unsigned byte buffers
|
||||
- FXAA is applied in-place on the framebuffer
|
||||
- Uses fixed parameters tuned for general-purpose rendering
|
||||
|
||||
## FXAA Parameters
|
||||
|
||||
The implementation uses the following parameters (matching VTK defaults):
|
||||
|
||||
- **RelativeContrastThreshold**: 0.125
|
||||
- **HardContrastThreshold**: 0.0625
|
||||
- **SubpixelBlendLimit**: 0.75
|
||||
- **SubpixelContrastThreshold**: 0.25
|
||||
- **EndpointSearchIterations**: 12
|
||||
|
||||
These parameters provide a good balance between quality and performance for typical 3D rendering scenarios.
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
A note about supported OpenGL versions:
|
||||
|
||||
Most documentation for the 7.x series of Mesa3D mentions OpenGL 2.1. The reason
|
||||
|
||||
@@ -8,6 +8,10 @@ add_executable(osdemo osdemo.c)
|
||||
target_link_libraries(osdemo osmesa ${M_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||
set_property(TARGET osdemo APPEND PROPERTY COMPILE_DEFINITIONS "GL_DLL_IMPORTS")
|
||||
|
||||
add_executable(osdemo_fxaa osdemo_fxaa.c)
|
||||
target_link_libraries(osdemo_fxaa osmesa ${M_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||
set_property(TARGET osdemo_fxaa APPEND PROPERTY COMPILE_DEFINITIONS "GL_DLL_IMPORTS")
|
||||
|
||||
# TODO -see if we can adapt this as an example:
|
||||
# https://github.com/danginsburg/opengles3-book/blob/master/Chapter_2/Hello_Triangle/Hello_Triangle.c
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* FXAA demonstration for OSMesa
|
||||
*
|
||||
* This example renders aliasing-prone 2D geometry (thin high-contrast edges,
|
||||
* rotated bars, single-pixel lines, and a fine checker grid) and applies a CPU
|
||||
* FXAA filter ported from VTK's `vtkFXAAFilterFS.glsl`. It writes three
|
||||
* images:
|
||||
*
|
||||
* <base>_nofxaa.png — original render
|
||||
* <base>_fxaa.png — FXAA filtered render
|
||||
* <base>_compare.png — side-by-side (left = no FXAA, right = FXAA)
|
||||
*
|
||||
* Usage:
|
||||
* osdemo_fxaa <base_filename> [width height]
|
||||
*
|
||||
* FXAA parameters match typical VTK usage and favor visible edge smoothing
|
||||
* while preserving detail:
|
||||
* - RelativeContrastThreshold: `0.125`
|
||||
* - HardContrastThreshold: `0.0625`
|
||||
* - SubpixelBlendLimit: `0.75`
|
||||
* - SubpixelContrastThreshold: `0.25`
|
||||
* - EndpointSearchIterations: `12`
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "OSMesa/gl.h"
|
||||
#include "OSMesa/osmesa.h"
|
||||
#include "svpng.h"
|
||||
|
||||
/* Write PNG via svpng (expects RGBA8 buffer, stride = width*4) */
|
||||
static void write_png(const char *filename, const unsigned char *buffer, int width, int height)
|
||||
{
|
||||
FILE *f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Failed to open '%s' for writing\n", filename);
|
||||
return;
|
||||
}
|
||||
svpng(f, width, height, buffer, 1);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* Render a 2D scene tailored to generate aliasing on high-contrast edges */
|
||||
static void render_aliasing_scene(int Width, int Height)
|
||||
{
|
||||
glDisable(GL_LIGHTING);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
#ifdef GL_MULTISAMPLE
|
||||
glDisable(GL_MULTISAMPLE);
|
||||
#endif
|
||||
|
||||
glViewport(0, 0, Width, Height);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0.0, (GLdouble)Width, 0.0, (GLdouble)Height, -1.0, 1.0);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
/* 1) Rotated thin white bar (long diagonal edges) */
|
||||
glPushMatrix();
|
||||
glTranslatef(Width * 0.5f, Height * 0.65f, 0.0f);
|
||||
glRotatef(17.0f, 0.f, 0.f, 1.f);
|
||||
glColor3f(1.f, 1.f, 1.f);
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(-180.f, -6.f);
|
||||
glVertex2f( 180.f, -6.f);
|
||||
glVertex2f( 180.f, 6.f);
|
||||
glVertex2f(-180.f, 6.f);
|
||||
glEnd();
|
||||
glPopMatrix();
|
||||
|
||||
/* 2) Single-pixel lines at multiple shallow angles around center */
|
||||
glLineWidth(1.f);
|
||||
glColor3f(1.f, 1.f, 0.f);
|
||||
glBegin(GL_LINES);
|
||||
const float cx = Width * 0.3f, cy = Height * 0.35f;
|
||||
for (int a = 10; a <= 80; a += 10) {
|
||||
float rad = (float)a * (float)M_PI / 180.0f;
|
||||
float len = (float)(Width * 0.35f);
|
||||
float dx = cosf(rad) * len;
|
||||
float dy = sinf(rad) * len;
|
||||
glVertex2f(cx - dx * 0.5f, cy - dy * 0.5f);
|
||||
glVertex2f(cx + dx * 0.5f, cy + dy * 0.5f);
|
||||
}
|
||||
glEnd();
|
||||
|
||||
/* 3) Fine wireframe box rotated slightly to force subpixel edges */
|
||||
glPushMatrix();
|
||||
glTranslatef(Width * 0.7f, Height * 0.35f, 0.0f);
|
||||
glRotatef(-13.0f, 0.f, 0.f, 1.f);
|
||||
glColor3f(0.f, 1.f, 1.f);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex2f(-120.f, -90.f);
|
||||
glVertex2f( 120.f, -90.f);
|
||||
glVertex2f( 120.f, 90.f);
|
||||
glVertex2f(-120.f, 90.f);
|
||||
glEnd();
|
||||
|
||||
/* Internal wireframe grid */
|
||||
glColor3f(0.f, 1.f, 1.f);
|
||||
glBegin(GL_LINES);
|
||||
for (int i = -100; i <= 100; i += 10) {
|
||||
glVertex2f((float)i, -90.f);
|
||||
glVertex2f((float)i, 90.f);
|
||||
}
|
||||
for (int j = -80; j <= 80; j += 10) {
|
||||
glVertex2f(-120.f, (float)j);
|
||||
glVertex2f( 120.f, (float)j);
|
||||
}
|
||||
glEnd();
|
||||
glPopMatrix();
|
||||
|
||||
/* 4) A small rotated checker region (alternating quads) for subpixel aliasing */
|
||||
glPushMatrix();
|
||||
glTranslatef(Width * 0.5f, Height * 0.25f, 0.0f);
|
||||
glRotatef(7.0f, 0.f, 0.f, 1.f);
|
||||
const int cw = 6, ch = 6, cols = 26, rows = 18;
|
||||
for (int y = 0; y < rows; ++y) {
|
||||
for (int x = 0; x < cols; ++x) {
|
||||
int parity = (x + y) & 1;
|
||||
glColor3f(parity ? 1.f : 0.f, parity ? 1.f : 0.f, parity ? 1.f : 0.f);
|
||||
float x0 = (float)(x * cw) - (float)(cols * cw) * 0.5f;
|
||||
float y0 = (float)(y * ch) - (float)(rows * ch) * 0.5f;
|
||||
float x1 = x0 + cw, y1 = y0 + ch;
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(x0, y0);
|
||||
glVertex2f(x1, y0);
|
||||
glVertex2f(x1, y1);
|
||||
glVertex2f(x0, y1);
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
|
||||
glFinish();
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage:\n osdemo_fxaa <base_filename> [width height]\n");
|
||||
fprintf(stderr, "\nThis demo now uses the integrated OSMesa FXAA feature.\n");
|
||||
fprintf(stderr, "It writes:\n");
|
||||
fprintf(stderr, " <base>_nofxaa.png - original render (no FXAA)\n");
|
||||
fprintf(stderr, " <base>_fxaa.png - FXAA with sRGB conversion (matching VTK)\n");
|
||||
fprintf(stderr, " <base>_compare.png - side-by-side comparison\n");
|
||||
return 1;
|
||||
}
|
||||
const char *base = argv[1];
|
||||
int Width = (argc >= 4) ? atoi(argv[2]) : 800;
|
||||
int Height = (argc >= 4) ? atoi(argv[3]) : 600;
|
||||
|
||||
OSMesaContext ctx = OSMesaCreateContextExt(OSMESA_RGBA, 0, 0, 0, NULL);
|
||||
if (!ctx) {
|
||||
fprintf(stderr, "OSMesaCreateContext failed!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Allocate buffer */
|
||||
unsigned char *buffer = (unsigned char *)calloc((size_t)Width * (size_t)Height, 4);
|
||||
if (!buffer) {
|
||||
fprintf(stderr, "Alloc image buffer failed!\n");
|
||||
OSMesaDestroyContext(ctx);
|
||||
return 1;
|
||||
}
|
||||
if (!OSMesaMakeCurrent(ctx, buffer, GL_UNSIGNED_BYTE, Width, Height)) {
|
||||
fprintf(stderr, "OSMesaMakeCurrent failed!\n");
|
||||
free(buffer);
|
||||
OSMesaDestroyContext(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Avoid the need to flip pixels (origin at top-left) */
|
||||
OSMesaPixelStore(OSMESA_Y_UP, 0);
|
||||
|
||||
/* Render aliasing-prone scene WITHOUT FXAA first */
|
||||
render_aliasing_scene(Width, Height);
|
||||
|
||||
/* Keep a copy of the pre-FXAA buffer */
|
||||
size_t rowBytes = (size_t)Width * 4;
|
||||
unsigned char *buffer_pre = (unsigned char *)malloc((size_t)Height * rowBytes);
|
||||
if (!buffer_pre) {
|
||||
fprintf(stderr, "Alloc pre buffer failed!\n");
|
||||
free(buffer);
|
||||
OSMesaDestroyContext(ctx);
|
||||
return 1;
|
||||
}
|
||||
memcpy(buffer_pre, buffer, (size_t)Height * rowBytes);
|
||||
|
||||
/* Now enable integrated FXAA and re-render the scene */
|
||||
OSMesaFXAAEnable(GL_TRUE);
|
||||
|
||||
/* Clear and re-render with FXAA enabled */
|
||||
render_aliasing_scene(Width, Height);
|
||||
|
||||
/* Write individual images */
|
||||
char fn_nofxaa[512], fn_fxaa[512], fn_compare[512];
|
||||
snprintf(fn_nofxaa, sizeof(fn_nofxaa), "%s_nofxaa.png", base);
|
||||
snprintf(fn_fxaa, sizeof(fn_fxaa), "%s_fxaa.png", base);
|
||||
snprintf(fn_compare,sizeof(fn_compare),"%s_compare.png",base);
|
||||
|
||||
write_png(fn_nofxaa, buffer_pre, Width, Height);
|
||||
write_png(fn_fxaa, buffer, Width, Height);
|
||||
|
||||
/* Compose side-by-side comparison */
|
||||
unsigned char *comp = (unsigned char *)calloc((size_t)Height, (size_t)Width * 8);
|
||||
if (comp) {
|
||||
for (int y = 0; y < Height; ++y) {
|
||||
unsigned char *dst = comp + (size_t)y * (size_t)(Width * 8);
|
||||
const unsigned char *srcL = buffer_pre + (size_t)y * rowBytes;
|
||||
const unsigned char *srcR = buffer + (size_t)y * rowBytes;
|
||||
memcpy(dst, srcL, rowBytes);
|
||||
memcpy(dst + rowBytes, srcR, rowBytes);
|
||||
}
|
||||
write_png(fn_compare, comp, Width * 2, Height);
|
||||
free(comp);
|
||||
} else {
|
||||
fprintf(stderr, "Warning: could not allocate compare buffer; skipping side-by-side image.\n");
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
free(buffer_pre);
|
||||
free(buffer);
|
||||
OSMesaDestroyContext(ctx);
|
||||
|
||||
fprintf(stdout, "Wrote:\n %s\n %s\n %s\n", fn_nofxaa, fn_fxaa, fn_compare);
|
||||
fprintf(stdout, "\nNote: FXAA was applied using OSMesaFXAAEnable() with sRGB color space\n");
|
||||
fprintf(stdout, "conversion (matching VTK's approach for improved visual quality).\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -269,6 +269,25 @@ OSMesaGetProcAddress(const char *funcName);
|
||||
GLAPI void GLAPIENTRY
|
||||
OSMesaColorClamp(GLboolean enable);
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable FXAA (Fast Approximate Anti-Aliasing) post-processing.
|
||||
* When enabled, FXAA is applied automatically after rendering operations
|
||||
* that flush the pipeline (e.g., glFinish, glFlush).
|
||||
*
|
||||
* FXAA smooths jagged edges in rendered images with minimal performance impact.
|
||||
* It works best on high-contrast edges and geometric shapes.
|
||||
*
|
||||
* The implementation converts to sRGB color space before processing (matching
|
||||
* VTK's approach) and converts back to linear RGB after processing.
|
||||
*
|
||||
* Input: enable - GL_TRUE to enable FXAA, GL_FALSE to disable
|
||||
*
|
||||
* New in Mesa 6.5.3+
|
||||
*/
|
||||
GLAPI void GLAPIENTRY
|
||||
OSMesaFXAAEnable(GLboolean enable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9,12 +9,14 @@ include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/swrast_setup
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shader/grammar
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shader/slang
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fxaa
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(osmesa_srcs
|
||||
drivers/common/driverfuncs.c
|
||||
drivers/osmesa/osmesa.c
|
||||
fxaa/fxaa_cpu.c
|
||||
glapi/glapi.c
|
||||
glapi/glthread.c
|
||||
main/accum.c
|
||||
@@ -186,6 +188,7 @@ set(osmesa_srcs
|
||||
)
|
||||
|
||||
add_library(osmesa SHARED ${osmesa_srcs})
|
||||
target_link_libraries(osmesa ${M_LIBRARY})
|
||||
install(TARGETS osmesa
|
||||
RUNTIME DESTINATION ${BIN_DIR}
|
||||
LIBRARY DESTINATION ${LIB_DIR}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "tnl/t_pipeline.h"
|
||||
#include "drivers/common/driverfuncs.h"
|
||||
#include "vbo/vbo.h"
|
||||
#include "fxaa/fxaa_cpu.h"
|
||||
|
||||
|
||||
|
||||
@@ -68,6 +69,7 @@ struct osmesa_context {
|
||||
GLvoid *rowaddr[MAX_HEIGHT]; /*< address of first pixel in each image row */
|
||||
GLboolean yup; /*< TRUE -> Y increases upward */
|
||||
/*< FALSE -> Y increases downward */
|
||||
GLboolean enable_fxaa; /*< TRUE to enable FXAA post-processing */
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +116,66 @@ osmesa_update_state(GLcontext *ctx, GLuint new_state)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply FXAA post-processing if enabled.
|
||||
* Called from glFinish to process the final rendered image.
|
||||
*/
|
||||
static void
|
||||
osmesa_apply_fxaa(OSMesaContext osmesa)
|
||||
{
|
||||
if (!osmesa || !osmesa->enable_fxaa)
|
||||
return;
|
||||
|
||||
/* Only apply FXAA to RGBA8 buffers */
|
||||
if (osmesa->rb->DataType != GL_UNSIGNED_BYTE)
|
||||
return;
|
||||
|
||||
/* Only apply to RGBA format for now */
|
||||
if (osmesa->format != OSMESA_RGBA)
|
||||
return;
|
||||
|
||||
GLint width = osmesa->rb->Width;
|
||||
GLint height = osmesa->rb->Height;
|
||||
uint8_t* buffer = (uint8_t*)osmesa->rb->Data;
|
||||
|
||||
if (!buffer || width <= 0 || height <= 0)
|
||||
return;
|
||||
|
||||
/* Calculate stride based on row length */
|
||||
GLint rowlength = osmesa->userRowLength ? osmesa->userRowLength : width;
|
||||
GLint strideBytes = rowlength * 4; /* 4 bytes per RGBA pixel */
|
||||
|
||||
/* Set up FXAA parameters (matching VTK defaults) */
|
||||
FXAAParams params = {
|
||||
.RelativeContrastThreshold = 0.125f,
|
||||
.HardContrastThreshold = 0.0625f,
|
||||
.SubpixelBlendLimit = 0.75f,
|
||||
.SubpixelContrastThreshold = 0.25f,
|
||||
.EndpointSearchIterations = 12
|
||||
};
|
||||
|
||||
/* Apply FXAA with sRGB color space conversion (matching VTK) */
|
||||
ImageRGBA8 img = { buffer, width, height, strideBytes };
|
||||
fxaa_apply_rgba8_srgb(&img, &img, ¶ms);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by glFinish to flush rendering and apply post-processing.
|
||||
*/
|
||||
static void
|
||||
osmesa_finish(GLcontext *ctx)
|
||||
{
|
||||
OSMesaContext osmesa = OSMESA_CONTEXT(ctx);
|
||||
|
||||
/* Finish all pending rendering operations first */
|
||||
_swrast_flush(ctx);
|
||||
|
||||
/* Apply FXAA if enabled */
|
||||
osmesa_apply_fxaa(osmesa);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************/
|
||||
/***** Read/write spans/arrays of pixels *****/
|
||||
@@ -1186,6 +1248,7 @@ OSMesaCreateContextExt(GLenum format, GLint depthBits, GLint stencilBits,
|
||||
functions.GetString = get_string;
|
||||
functions.UpdateState = osmesa_update_state;
|
||||
functions.GetBufferSize = NULL;
|
||||
functions.Finish = osmesa_finish;
|
||||
|
||||
if (!_mesa_initialize_context(&osmesa->mesa,
|
||||
osmesa->gl_visual,
|
||||
@@ -1230,6 +1293,7 @@ OSMesaCreateContextExt(GLenum format, GLint depthBits, GLint stencilBits,
|
||||
osmesa->gInd = gind;
|
||||
osmesa->bInd = bind;
|
||||
osmesa->aInd = aind;
|
||||
osmesa->enable_fxaa = GL_FALSE;
|
||||
|
||||
/* Initialize the software rasterizer and helper modules. */
|
||||
{
|
||||
@@ -1547,6 +1611,7 @@ static struct name_function functions[] = {
|
||||
{ "OSMesaGetColorBuffer", (OSMESAproc) OSMesaGetColorBuffer },
|
||||
{ "OSMesaGetProcAddress", (OSMESAproc) OSMesaGetProcAddress },
|
||||
{ "OSMesaColorClamp", (OSMESAproc) OSMesaColorClamp },
|
||||
{ "OSMesaFXAAEnable", (OSMESAproc) OSMesaFXAAEnable },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
@@ -1576,6 +1641,19 @@ OSMesaColorClamp(GLboolean enable)
|
||||
}
|
||||
|
||||
|
||||
GLAPI void GLAPIENTRY
|
||||
OSMesaFXAAEnable(GLboolean enable)
|
||||
{
|
||||
OSMesaContext osmesa = OSMesaGetCurrentContext();
|
||||
|
||||
if (!osmesa) {
|
||||
return;
|
||||
}
|
||||
|
||||
osmesa->enable_fxaa = enable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Port of VTK's vtkFXAAFilterFS.glsl to OSMesa.
|
||||
*
|
||||
* SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
* https://github.com/Kitware/VTK/blob/master/Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl
|
||||
*
|
||||
* CPU FXAA Post-Process (VTK endpoint search variant only)
|
||||
*
|
||||
* Endpoint search follows the higher-quality VTK algorithm (better single-pixel line handling).
|
||||
*/
|
||||
|
||||
#include "fxaa_cpu.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef FXAA_CLAMP
|
||||
#define FXAA_CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v)))
|
||||
#endif
|
||||
|
||||
typedef struct { float x, y; } vec2;
|
||||
|
||||
static inline float luminosity(float r, float g, float b) {
|
||||
return 0.299f * r + 0.587f * g + 0.114f * b;
|
||||
}
|
||||
|
||||
static inline void load_rgb_u8(const uint8_t* p, float* r, float* g, float* b) {
|
||||
*r = p[0] / 255.0f;
|
||||
*g = p[1] / 255.0f;
|
||||
*b = p[2] / 255.0f;
|
||||
}
|
||||
|
||||
static inline void store_rgb_u8(uint8_t* p, float r, float g, float b) {
|
||||
p[0] = (uint8_t)FXAA_CLAMP(lroundf(r * 255.0f), 0, 255);
|
||||
p[1] = (uint8_t)FXAA_CLAMP(lroundf(g * 255.0f), 0, 255);
|
||||
p[2] = (uint8_t)FXAA_CLAMP(lroundf(b * 255.0f), 0, 255);
|
||||
}
|
||||
|
||||
static inline uint8_t load_a_u8(const uint8_t* p) { return p[3]; }
|
||||
static inline void store_a_u8(uint8_t* p, uint8_t a) { p[3] = a; }
|
||||
|
||||
/* Bilinear sample: normalized coords [0,1] with clamp-to-edge */
|
||||
static void sample_bilinear_rgb(const ImageRGBA8* img, float u, float v, float* r, float* g, float* b) {
|
||||
float x = FXAA_CLAMP(u, 0.0f, 1.0f) * (float)(img->width - 1);
|
||||
float y = FXAA_CLAMP(v, 0.0f, 1.0f) * (float)(img->height - 1);
|
||||
int x0 = (int)floorf(x);
|
||||
int y0 = (int)floorf(y);
|
||||
int x1 = FXAA_CLAMP(x0 + 1, 0, img->width - 1);
|
||||
int y1 = FXAA_CLAMP(y0 + 1, 0, img->height - 1);
|
||||
float tx = x - (float)x0;
|
||||
float ty = y - (float)y0;
|
||||
|
||||
const uint8_t* p00 = img->rgba + y0 * img->strideBytes + x0 * 4;
|
||||
const uint8_t* p10 = img->rgba + y0 * img->strideBytes + x1 * 4;
|
||||
const uint8_t* p01 = img->rgba + y1 * img->strideBytes + x0 * 4;
|
||||
const uint8_t* p11 = img->rgba + y1 * img->strideBytes + x1 * 4;
|
||||
|
||||
float r00, g00, b00, r10, g10, b10, r01, g01, b01, r11, g11, b11;
|
||||
load_rgb_u8(p00, &r00, &g00, &b00);
|
||||
load_rgb_u8(p10, &r10, &g10, &b10);
|
||||
load_rgb_u8(p01, &r01, &g01, &b01);
|
||||
load_rgb_u8(p11, &r11, &g11, &b11);
|
||||
|
||||
float r0 = r00 + tx * (r10 - r00);
|
||||
float g0 = g00 + tx * (g10 - g00);
|
||||
float b0 = b00 + tx * (b10 - b00);
|
||||
|
||||
float r1 = r01 + tx * (r11 - r01);
|
||||
float g1 = g01 + tx * (g11 - g01);
|
||||
float b1 = b01 + tx * (b11 - b01);
|
||||
|
||||
*r = r0 + ty * (r1 - r0);
|
||||
*g = g0 + ty * (g1 - g0);
|
||||
*b = b0 + ty * (b1 - b0);
|
||||
}
|
||||
|
||||
/* Nearest sample at integer offsets for 3x3 neighborhood */
|
||||
static void sample_nearest_rgb(const ImageRGBA8* img, int ix, int iy, float* r, float* g, float* b) {
|
||||
ix = FXAA_CLAMP(ix, 0, img->width - 1);
|
||||
iy = FXAA_CLAMP(iy, 0, img->height - 1);
|
||||
const uint8_t* p = img->rgba + iy * img->strideBytes + ix * 4;
|
||||
load_rgb_u8(p, r, g, b);
|
||||
}
|
||||
|
||||
/* Endpoint search (VTK variant) — better single-pixel line handling */
|
||||
static int vtkEndpointSearchCPU(
|
||||
const ImageRGBA8* img,
|
||||
vec2 posC, float lumC, float lumHC, float lengthSign, vec2 tcPixel,
|
||||
int horzSpan, int maxIters, vec2* outPosEdgeAA
|
||||
) {
|
||||
vec2 posHC = posC;
|
||||
vec2 edgeDir = (vec2){0.f, 0.f};
|
||||
if (horzSpan) {
|
||||
posHC.y += lengthSign;
|
||||
edgeDir.x = tcPixel.x;
|
||||
} else {
|
||||
posHC.x += lengthSign;
|
||||
edgeDir.y = tcPixel.y;
|
||||
}
|
||||
|
||||
float lumHCN = lumHC, lumHCP = lumHC, lumCN = lumC, lumCP = lumC;
|
||||
int doneN = 0, doneP = 0;
|
||||
vec2 posHCN = (vec2){ posHC.x - edgeDir.x, posHC.y - edgeDir.y };
|
||||
vec2 posHCP = (vec2){ posHC.x + edgeDir.x, posHC.y + edgeDir.y };
|
||||
vec2 posCN = (vec2){ posC.x - edgeDir.x, posC.y - edgeDir.y };
|
||||
vec2 posCP = (vec2){ posC.x + edgeDir.x, posC.y + edgeDir.y };
|
||||
|
||||
for (int i = 0; i < maxIters; ++i) {
|
||||
if (!doneN) {
|
||||
float r,g,b;
|
||||
sample_bilinear_rgb(img, posHCN.x, posHCN.y, &r,&g,&b);
|
||||
lumHCN = luminosity(r,g,b);
|
||||
sample_bilinear_rgb(img, posCN.x, posCN.y, &r,&g,&b);
|
||||
lumCN = luminosity(r,g,b);
|
||||
}
|
||||
if (!doneP) {
|
||||
float r,g,b;
|
||||
sample_bilinear_rgb(img, posHCP.x, posHCP.y, &r,&g,&b);
|
||||
lumHCP = luminosity(r,g,b);
|
||||
sample_bilinear_rgb(img, posCP.x, posCP.y, &r,&g,&b);
|
||||
lumCP = luminosity(r,g,b);
|
||||
}
|
||||
doneN = doneN || fabsf(lumHCN - lumHC) > fabsf(lumHCN - lumC)
|
||||
|| fabsf(lumCN - lumC) > fabsf(lumCN - lumHC);
|
||||
doneP = doneP || fabsf(lumHCP - lumHC) > fabsf(lumHCP - lumC)
|
||||
|| fabsf(lumCP - lumC) > fabsf(lumCP - lumHC);
|
||||
if (doneN && doneP) break;
|
||||
if (!doneN) { posHCN.x -= edgeDir.x; posHCN.y -= edgeDir.y; posCN.x -= edgeDir.x; posCN.y -= edgeDir.y; }
|
||||
if (!doneP) { posHCP.x += edgeDir.x; posHCP.y += edgeDir.y; posCP.x += edgeDir.x; posCP.y += edgeDir.y; }
|
||||
}
|
||||
|
||||
float dstN, dstP;
|
||||
if (horzSpan) { dstN = posC.x - posCN.x; dstP = posCP.x - posC.x; }
|
||||
else { dstN = posC.y - posCN.y; dstP = posCP.y - posC.y; }
|
||||
|
||||
int nearestEndpointIsN = dstN < dstP;
|
||||
float dst = fminf(dstN, dstP);
|
||||
float lumCNear = nearestEndpointIsN ? lumCN : lumCP;
|
||||
|
||||
int needEdgeAA = fabsf(lumCNear - lumHC) < fabsf(lumCNear - lumC);
|
||||
|
||||
float invNegSpanLength = -1.f / (dstN + dstP);
|
||||
float pixelOffset = dst * invNegSpanLength + 0.5f;
|
||||
|
||||
*outPosEdgeAA = posC;
|
||||
if (horzSpan) outPosEdgeAA->y += pixelOffset * lengthSign;
|
||||
else outPosEdgeAA->x += pixelOffset * lengthSign;
|
||||
|
||||
return needEdgeAA ? 1 : 0;
|
||||
}
|
||||
|
||||
void fxaa_apply_rgba8(const ImageRGBA8* in, ImageRGBA8* out, const FXAAParams* p) {
|
||||
// If out == in, use a temp buffer to avoid read/write hazards.
|
||||
uint8_t* temp = NULL;
|
||||
ImageRGBA8 dst = *out;
|
||||
|
||||
int inPlace = (in->rgba == out->rgba);
|
||||
if (inPlace) {
|
||||
temp = (uint8_t*)malloc((size_t)in->strideBytes * (size_t)in->height);
|
||||
if (!temp) return;
|
||||
dst.rgba = temp;
|
||||
dst.width = in->width;
|
||||
dst.height = in->height;
|
||||
dst.strideBytes = in->strideBytes;
|
||||
}
|
||||
|
||||
const float tcPixelX = 1.0f / (float)in->width;
|
||||
const float tcPixelY = 1.0f / (float)in->height;
|
||||
const vec2 tcPixel = { tcPixelX, tcPixelY };
|
||||
|
||||
for (int y = 0; y < in->height; ++y) {
|
||||
for (int x = 0; x < in->width; ++x) {
|
||||
vec2 tcC = { (float)x * tcPixelX, (float)y * tcPixelY };
|
||||
|
||||
int xW = FXAA_CLAMP(x - 1, 0, in->width - 1);
|
||||
int xE = FXAA_CLAMP(x + 1, 0, in->width - 1);
|
||||
int yN = FXAA_CLAMP(y - 1, 0, in->height - 1);
|
||||
int yS = FXAA_CLAMP(y + 1, 0, in->height - 1);
|
||||
|
||||
const uint8_t* pC = in->rgba + y * in->strideBytes + x * 4;
|
||||
float rC,gC,bC; load_rgb_u8(pC, &rC,&gC,&bC);
|
||||
uint8_t aC = load_a_u8(pC);
|
||||
|
||||
float rN,gN,bN, rS,gS,bS, rW,gW,bW, rE,gE,bE;
|
||||
sample_nearest_rgb(in, x, yN, &rN,&gN,&bN);
|
||||
sample_nearest_rgb(in, x, yS, &rS,&gS,&bS);
|
||||
sample_nearest_rgb(in, xW, y, &rW,&gW,&bW);
|
||||
sample_nearest_rgb(in, xE, y, &rE,&gE,&bE);
|
||||
|
||||
float rNE,gNE,bNE, rSE,gSE,bSE, rNW,gNW,bNW, rSW,gSW,bSW;
|
||||
sample_nearest_rgb(in, xE, yN, &rNE,&gNE,&bNE);
|
||||
sample_nearest_rgb(in, xE, yS, &rSE,&gSE,&bSE);
|
||||
sample_nearest_rgb(in, xW, yN, &rNW,&gNW,&bNW);
|
||||
sample_nearest_rgb(in, xW, yS, &rSW,&gSW,&bSW);
|
||||
|
||||
float lumC = luminosity(rC,gC,bC);
|
||||
float lumN = luminosity(rN,gN,bN);
|
||||
float lumS = luminosity(rS,gS,bS);
|
||||
float lumW = luminosity(rW,gW,bW);
|
||||
float lumE = luminosity(rE,gE,bE);
|
||||
float lumNE = luminosity(rNE,gNE,bNE);
|
||||
float lumSE = luminosity(rSE,gSE,bSE);
|
||||
float lumNW = luminosity(rNW,gNW,bNW);
|
||||
float lumSW = luminosity(rSW,gSW,bSW);
|
||||
|
||||
float lumMin = fminf(lumC, fminf(fminf(lumN, lumS), fminf(lumW, lumE)));
|
||||
float lumMax = fmaxf(lumC, fmaxf(fmaxf(lumN, lumS), fmaxf(lumW, lumE)));
|
||||
float lumRange = lumMax - lumMin;
|
||||
float lumThresh = fmaxf(p->HardContrastThreshold,
|
||||
p->RelativeContrastThreshold * lumMax);
|
||||
|
||||
float outR = rC, outG = gC, outB = bC;
|
||||
|
||||
if (lumRange >= lumThresh) {
|
||||
float lumNS = lumN + lumS;
|
||||
float lumWE = lumW + lumE;
|
||||
float lumNSWE = lumNS + lumWE;
|
||||
float lumNWNE = lumNW + lumNE;
|
||||
float lumSWSE = lumSW + lumSE;
|
||||
float lumNWSW = lumNW + lumSW;
|
||||
float lumNESE = lumNE + lumSE;
|
||||
|
||||
float lumAveNSWE = 0.25f * lumNSWE;
|
||||
float lumSubRange = fabsf(lumAveNSWE - lumC);
|
||||
float blendSub = fmaxf(0.f, (lumSubRange / lumRange) - p->SubpixelContrastThreshold);
|
||||
blendSub = fminf(p->SubpixelBlendLimit,
|
||||
blendSub * (1.f / (1.f - p->SubpixelContrastThreshold)));
|
||||
|
||||
float edgeVertRow1 = fabsf(-2.f * lumN + lumNWNE);
|
||||
float edgeVertRow2 = fabsf(-2.f * lumC + lumWE);
|
||||
float edgeVertRow3 = fabsf(-2.f * lumS + lumSWSE);
|
||||
float edgeVert = ((2.f * edgeVertRow2 + edgeVertRow1) + edgeVertRow3) / 12.f;
|
||||
|
||||
float edgeHorzCol1 = fabsf(-2.f * lumW + lumNWSW);
|
||||
float edgeHorzCol2 = fabsf(-2.f * lumC + lumNS);
|
||||
float edgeHorzCol3 = fabsf(-2.f * lumE + lumNESE);
|
||||
float edgeHorz = ((2.f * edgeHorzCol2 + edgeHorzCol1) + edgeHorzCol3) / 12.f;
|
||||
|
||||
int horzSpan = edgeHorz >= edgeVert;
|
||||
|
||||
float lumHC1, lumHC2;
|
||||
float lengthSign;
|
||||
if (horzSpan) {
|
||||
lumHC1 = lumN; lumHC2 = lumS; lengthSign = -tcPixelY; // assume N
|
||||
} else {
|
||||
lumHC1 = lumW; lumHC2 = lumE; lengthSign = -tcPixelX; // assume W
|
||||
}
|
||||
float lumHC = lumHC1;
|
||||
if (fabsf(lumC - lumHC1) < fabsf(lumC - lumHC2)) {
|
||||
lumHC = lumHC2;
|
||||
lengthSign = -lengthSign;
|
||||
}
|
||||
|
||||
vec2 posEdgeAA;
|
||||
int needEdgeAA = vtkEndpointSearchCPU(in, tcC, lumC, lumHC, lengthSign, tcPixel, horzSpan,
|
||||
p->EndpointSearchIterations, &posEdgeAA);
|
||||
|
||||
float rEdge,gEdge,bEdge;
|
||||
if (needEdgeAA) {
|
||||
sample_bilinear_rgb(in, posEdgeAA.x, posEdgeAA.y, &rEdge,&gEdge,&bEdge);
|
||||
} else {
|
||||
rEdge = rC; gEdge = gC; bEdge = bC;
|
||||
}
|
||||
|
||||
float rSub = (1.f/9.f) * (rNW + rN + rNE + rW + rC + rE + rSW + rS + rSE);
|
||||
float gSub = (1.f/9.f) * (gNW + gN + gNE + gW + gC + gE + gSW + gS + gSE);
|
||||
float bSub = (1.f/9.f) * (bNW + bN + bNE + bW + bC + bE + bSW + bS + bSE);
|
||||
|
||||
outR = rEdge + blendSub * (rSub - rEdge);
|
||||
outG = gEdge + blendSub * (gSub - gEdge);
|
||||
outB = bEdge + blendSub * (bSub - bEdge);
|
||||
}
|
||||
|
||||
uint8_t* pOut = dst.rgba + y * dst.strideBytes + x * 4;
|
||||
store_rgb_u8(pOut, outR, outG, outB);
|
||||
store_a_u8(pOut, aC);
|
||||
}
|
||||
}
|
||||
|
||||
if (inPlace) {
|
||||
memcpy(out->rgba, dst.rgba, (size_t)dst.strideBytes * (size_t)dst.height);
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/* sRGB <-> Linear RGB conversion functions */
|
||||
static inline float linear_to_srgb(float linear) {
|
||||
if (linear <= 0.0031308f)
|
||||
return linear * 12.92f;
|
||||
else
|
||||
return 1.055f * powf(linear, 1.0f/2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
static inline float srgb_to_linear(float srgb) {
|
||||
if (srgb <= 0.04045f)
|
||||
return srgb / 12.92f;
|
||||
else
|
||||
return powf((srgb + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
void fxaa_apply_rgba8_srgb(const ImageRGBA8* in, ImageRGBA8* out, const FXAAParams* p) {
|
||||
/*
|
||||
* Allocate temporary buffers for sRGB conversion.
|
||||
*
|
||||
* NOTE: This implementation uses two temporary buffers for clarity and correctness.
|
||||
* Future optimization: Could reduce memory overhead by:
|
||||
* 1. Processing in tiles to reduce buffer size
|
||||
* 2. Combining conversion passes where possible
|
||||
* 3. Using SIMD for faster conversion
|
||||
* Current approach prioritizes correctness and code clarity.
|
||||
*/
|
||||
size_t bufferSize = (size_t)in->strideBytes * (size_t)in->height;
|
||||
uint8_t* srgbBuffer = (uint8_t*)malloc(bufferSize);
|
||||
if (!srgbBuffer) return;
|
||||
|
||||
/* Convert linear RGB to sRGB */
|
||||
for (int y = 0; y < in->height; ++y) {
|
||||
for (int x = 0; x < in->width; ++x) {
|
||||
const uint8_t* pIn = in->rgba + y * in->strideBytes + x * 4;
|
||||
uint8_t* pSrgb = srgbBuffer + y * in->strideBytes + x * 4;
|
||||
|
||||
float r = pIn[0] / 255.0f;
|
||||
float g = pIn[1] / 255.0f;
|
||||
float b = pIn[2] / 255.0f;
|
||||
|
||||
r = linear_to_srgb(r);
|
||||
g = linear_to_srgb(g);
|
||||
b = linear_to_srgb(b);
|
||||
|
||||
pSrgb[0] = (uint8_t)FXAA_CLAMP(lroundf(r * 255.0f), 0, 255);
|
||||
pSrgb[1] = (uint8_t)FXAA_CLAMP(lroundf(g * 255.0f), 0, 255);
|
||||
pSrgb[2] = (uint8_t)FXAA_CLAMP(lroundf(b * 255.0f), 0, 255);
|
||||
pSrgb[3] = pIn[3]; /* Preserve alpha */
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply FXAA in sRGB space */
|
||||
ImageRGBA8 srgbImg = { srgbBuffer, in->width, in->height, in->strideBytes };
|
||||
uint8_t* fxaaBuffer = (uint8_t*)malloc(bufferSize);
|
||||
if (!fxaaBuffer) {
|
||||
free(srgbBuffer);
|
||||
return;
|
||||
}
|
||||
ImageRGBA8 fxaaImg = { fxaaBuffer, in->width, in->height, in->strideBytes };
|
||||
|
||||
fxaa_apply_rgba8(&srgbImg, &fxaaImg, p);
|
||||
|
||||
/* Convert sRGB back to linear RGB */
|
||||
for (int y = 0; y < in->height; ++y) {
|
||||
for (int x = 0; x < in->width; ++x) {
|
||||
const uint8_t* pSrgb = fxaaBuffer + y * in->strideBytes + x * 4;
|
||||
uint8_t* pOut = out->rgba + y * out->strideBytes + x * 4;
|
||||
|
||||
float r = pSrgb[0] / 255.0f;
|
||||
float g = pSrgb[1] / 255.0f;
|
||||
float b = pSrgb[2] / 255.0f;
|
||||
|
||||
r = srgb_to_linear(r);
|
||||
g = srgb_to_linear(g);
|
||||
b = srgb_to_linear(b);
|
||||
|
||||
pOut[0] = (uint8_t)FXAA_CLAMP(lroundf(r * 255.0f), 0, 255);
|
||||
pOut[1] = (uint8_t)FXAA_CLAMP(lroundf(g * 255.0f), 0, 255);
|
||||
pOut[2] = (uint8_t)FXAA_CLAMP(lroundf(b * 255.0f), 0, 255);
|
||||
pOut[3] = pSrgb[3]; /* Preserve alpha */
|
||||
}
|
||||
}
|
||||
|
||||
free(srgbBuffer);
|
||||
free(fxaaBuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* mode: C
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8 cino=N-s
|
||||
*/
|
||||
@@ -0,0 +1,58 @@
|
||||
/* Port of VTK's vtkFXAAFilterFS.glsl to OSMesa.
|
||||
*
|
||||
* SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
* https://github.com/Kitware/VTK/blob/master/Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl
|
||||
*/
|
||||
|
||||
#ifndef FXAA_CPU_H
|
||||
#define FXAA_CPU_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
float RelativeContrastThreshold; // e.g. 0.125f
|
||||
float HardContrastThreshold; // e.g. 0.0625f
|
||||
float SubpixelBlendLimit; // e.g. 0.75f
|
||||
float SubpixelContrastThreshold; // e.g. 0.25f
|
||||
int EndpointSearchIterations; // e.g. 12
|
||||
} FXAAParams;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* rgba; // pointer to RGBA8 pixels
|
||||
int width;
|
||||
int height;
|
||||
int strideBytes; // bytes per row (>= width * 4)
|
||||
} ImageRGBA8;
|
||||
|
||||
/* Apply FXAA to an RGBA8 buffer (can be in-place if out == in). */
|
||||
void fxaa_apply_rgba8(const ImageRGBA8* in, ImageRGBA8* out, const FXAAParams* p);
|
||||
|
||||
/*
|
||||
* Apply FXAA with sRGB color space conversion (matching VTK's approach).
|
||||
* Converts linear RGB to sRGB before FXAA, then back to linear after.
|
||||
* This improves visual quality by processing in perceptual color space.
|
||||
* Can be in-place if out == in.
|
||||
*/
|
||||
void fxaa_apply_rgba8_srgb(const ImageRGBA8* in, ImageRGBA8* out, const FXAAParams* p);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FXAA_CPU_H */
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* mode: C
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8 cino=N-s
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user