v2:Move implementation to ubo.cpp and ubo.h as suggested by Brian Paul --- src/mesa/main/api_exec.c | 2 + src/mesa/main/bufferobj.c | 11 + src/mesa/main/ubo.c | 268 ++++++++++++++++++++++++++ src/mesa/main/ubo.cpp | 263 +++++++++++++++++++++++++ src/mesa/main/ubo.h | 33 +++ src/mesa/sources.mak | 1 + src/mesa/state_tracker/st_cb_bufferobjects.c | 6 +- 7 files changed, 583 insertions(+), 1 deletions(-) create mode 100644 src/mesa/main/ubo.c create mode 100644 src/mesa/main/ubo.cpp create mode 100644 src/mesa/main/ubo.h
diff --git a/src/mesa/main/api_exec.c b/src/mesa/main/api_exec.c index e0bf90d..c908719 100644 --- a/src/mesa/main/api_exec.c +++ b/src/mesa/main/api_exec.c @@ -102,6 +102,7 @@ #if FEATURE_ARB_shader_objects #include "shaderapi.h" #include "uniforms.h" +#include "ubo.h" #endif #include "syncobj.h" #include "main/dispatch.h" @@ -307,6 +308,7 @@ _mesa_create_exec_table(void) #if FEATURE_ARB_shader_objects _mesa_init_shader_dispatch(exec); _mesa_init_shader_uniform_dispatch(exec); + _mesa_init_shader_uniform_buffer_objects(exec); #endif /* 2. GL_EXT_blend_color */ diff --git a/src/mesa/main/bufferobj.c b/src/mesa/main/bufferobj.c index b7e59e8..d74b78c 100644 --- a/src/mesa/main/bufferobj.c +++ b/src/mesa/main/bufferobj.c @@ -98,6 +98,8 @@ get_buffer_target(struct gl_context *ctx, GLenum target) return &ctx->Texture.BufferObject; } break; + case GL_UNIFORM_BUFFER: + return &ctx->UniformBufferObject.UniformObj; default: return NULL; } @@ -734,6 +736,10 @@ _mesa_BindBufferBase(GLenum target, GLuint index, GLuint buffer) break; #endif +#if FEATURE_ARB_uniform_buffer_object + case GL_UNIFORM_BUFFER: + break; +#endif default: _mesa_error(ctx, GL_INVALID_ENUM, "glBindBufferBase(target)"); break; @@ -756,6 +762,11 @@ _mesa_BindBufferBase(GLenum target, GLuint index, GLuint buffer) _mesa_bind_buffer_range_for_transform_feedback(ctx, index, bufObj, 0, size); break; #endif +#if FEATURE_ARB_uniform_buffer_object + case GL_UNIFORM_BUFFER: + ctx->UniformBufferObject.BindingPoint[index] = bufObj; + break; +#endif default: /* should not go here */ break; } diff --git a/src/mesa/main/ubo.c b/src/mesa/main/ubo.c new file mode 100644 index 0000000..1e31df8 --- /dev/null +++ b/src/mesa/main/ubo.c @@ -0,0 +1,268 @@ +/* + * Mesa 3-D graphics library + * + * Copyright (C) 2004-2011 Brian Paul All Rights Reserved. + * Copyright (C) 2009-2011 VMware, Inc. All Rights Reserved. + * Copyright © 2010-2011 Intel Corporation + * Copyright (C) 2011 Vincent Lejeune. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * API implementation of GL_ARB_uniform_buffer_object. + * + * Every uniform (in an UBO or not) is given an index that identifies it when + * calling one of these functions. Strictly speaking, such an index doesn't + * need to match location for classic uniform ; others drivers however use this + * approach so we're also matching them for compatibility purpose. + */ + +#include <stdint.h> +#include "main/glheader.h" +#include "main/context.h" +#include "main/dispatch.h" +#include "main/image.h" +#include "main/mfeatures.h" +#include "main/mtypes.h" +#include "main/shaderapi.h" +#include "main/shaderobj.h" +#include "main/uniforms.h" +#include "main/hash.h" +#include "program/prog_parameter.h" +#include "program/prog_statevars.h" +#include "program/prog_uniform.h" +#include "program/prog_instruction.h" +#include "main/ubo.h" +#include "main/bufferobj.h" + +static void +get_ubo_info (struct gl_context *ctx, + struct gl_shader_program *shProg, GLint ubo_index, + GLenum query, int *data) +{ + if (ubo_index > shProg->UBOCount || ubo_index < 0) + _mesa_error (ctx, GL_INVALID_VALUE, + "glGetActiveUniformBlock(uniformBlockIndex)"); + + struct gl_uniform_buffer_object current_ubo = + shProg->UniformBufferObject[ubo_index]; + + switch (query) + { + case GL_UNIFORM_BLOCK_BINDING: + //*data = current_ubo.BoundBuffer; + break; + case GL_UNIFORM_BLOCK_DATA_SIZE: + //*data = 0; + break; + case GL_UNIFORM_BLOCK_NAME_LENGTH: + *data = strlen (current_ubo.Name); + break; + case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: + *data = current_ubo.NumberOfVariables; + break; + case GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER: + case GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER: + case GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER: + //*data = 0; + break; + default: + break; + } +} + +static GLuint +get_indice (const struct gl_shader_program *prog, const char *name) +{ + void* index = + hash_table_find (prog->Uniforms->NamedAccessUBOVariables, name); + if (index) { + uintptr_t faked_index = (uintptr_t) index; + return faked_index - 1; + } + + return GL_INVALID_INDEX; +} + +/** + * This function checks if it can copy source into dest. + * If it cannot, it returns 1, otherwise it returns 0 and + * write size to *length if non NULL + */ +static int +copy_char_buffer (const char *source, char *dest, unsigned destsize, + int *length) +{ + unsigned sourcesize = strlen (source); + if (sourcesize > destsize + 1) + { + return -1; + } + memcpy (dest, source, sourcesize); + dest[sourcesize] = '0'; + if (length) + *length = sourcesize; + return 0; +} + +static void +get_ubo_name (struct gl_context *ctx, struct gl_shader_program *shProg, + GLint index, GLsizei bufsize, GLsizei * length, char *buffer) +{ + if (index >= shProg->UBOCount || index < 0) + { + _mesa_error (ctx, GL_INVALID_VALUE, + "GetActiveUniformBlockName(uniformBlockIndex)"); + return; + } + + struct gl_uniform_buffer_object current_ubo = + shProg->UniformBufferObject[index]; + + if (copy_char_buffer (current_ubo.Name, buffer, bufsize, length)) + { + _mesa_error (ctx, GL_INVALID_VALUE, + "GetActiveUniformBlockName(bufSize)"); + return; + } + return; +} + +static void +get_uniform_variable_name (struct gl_context *ctx, + struct gl_shader_program *sh, GLuint index, + GLsizei bufsize, GLsizei * length, + char *uniformName) +{ + const char *name = sh->Uniforms->Uniforms[index].Name; + if (copy_char_buffer (name, uniformName, bufsize, length)) + { + _mesa_error (ctx, GL_INVALID_VALUE, "GetActiveUniformName(bufSize)"); + return; + } + return; +} + +static GLint +get_uniform_variable_info (struct gl_shader_program *sh, GLuint index, + GLenum pname) +{ + uint n = sh->Uniforms->NumUniforms; + if (index >= n) + { + struct gl_program_ubo_variable *var = + sh->Uniforms->IndexedUBOVariables[index - n]; + switch (pname) + { + case GL_UNIFORM_TYPE: + return var->Type; + case GL_UNIFORM_SIZE: + return 1; + case GL_UNIFORM_BLOCK_NAME_LENGTH: + case GL_UNIFORM_BLOCK_INDEX: + return var->UBO->Index; + case GL_UNIFORM_OFFSET: + return var->Offset; + case GL_UNIFORM_ARRAY_STRIDE: + case GL_UNIFORM_MATRIX_STRIDE: + return var->Stride; + case GL_UNIFORM_IS_ROW_MAJOR: + return sh->UniformBufferObject[var->UBO->Index].MatrixLayout == + UBO_MATRIX_LAYOUT_ROW_MAJOR; + } + } + + return GL_INVALID_VALUE; +} + +void GLAPIENTRY +_mesa_GetActiveUniformBlockInfo (GLuint program, GLuint ubo_index, + GLenum pname, GLint * params) +{ + GET_CURRENT_CONTEXT (ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err (ctx, program, "GetActiveUniformBlockiv"); + get_ubo_info (ctx, shProg, ubo_index, pname, params); +} + +void GLAPIENTRY +_mesa_GetActiveUniformBlockName (GLuint program, GLuint ubo_index, + GLsizei bufsize, GLint * length, char *name) +{ + GET_CURRENT_CONTEXT (ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err (ctx, program, + "GetActiveUniformBlockName"); + get_ubo_name (ctx, shProg, ubo_index, bufsize, length, name); +} + +void GLAPIENTRY +_mesa_GetUniformIndices (GLuint program, GLsizei number_of_variables, + const char **names, GLuint * indices) +{ + unsigned i; + GET_CURRENT_CONTEXT (ctx); + + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err (ctx, program, "GetUniformIndices"); + for (i = 0; i < number_of_variables; i++) + { + indices[i] = get_indice (shProg, names[i]); + } +} + +void GLAPIENTRY +_mesa_GetActiveUniformName (GLuint program, GLuint index, GLsizei bufsize, + GLsizei * length, char *uniformName) +{ + GET_CURRENT_CONTEXT (ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err (ctx, program, "GetActiveUniformName"); + get_uniform_variable_name (ctx, shProg, index, bufsize, length, + uniformName); + +} + +void GLAPIENTRY +_mesa_GetActiveUniformsiv (GLuint program, GLsizei uniformCount, + const GLuint * uniformIndices, GLenum pname, + GLint * param) +{ + unsigned k; + GET_CURRENT_CONTEXT (ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err (ctx, program, "GetActiveUniformsiv"); + + + for (k = 0; k < uniformCount; k++) + { + param[k] = get_uniform_variable_info (shProg, uniformIndices[k], pname); + } +} + +void +_mesa_init_shader_uniform_buffer_objects (struct _glapi_table *exec) +{ +/* GL_ARB_Uniform_Buffer_Object */ + SET_GetActiveUniformBlockiv (exec, _mesa_GetActiveUniformBlockInfo); + SET_GetActiveUniformBlockName (exec, _mesa_GetActiveUniformBlockName); + SET_GetUniformIndices (exec, _mesa_GetUniformIndices); + SET_GetActiveUniformName (exec, _mesa_GetActiveUniformName); + SET_GetActiveUniformsiv (exec, _mesa_GetActiveUniformsiv); +} diff --git a/src/mesa/main/ubo.cpp b/src/mesa/main/ubo.cpp new file mode 100644 index 0000000..77f7edd --- /dev/null +++ b/src/mesa/main/ubo.cpp @@ -0,0 +1,263 @@ +/* + * Mesa 3-D graphics library + * + * Copyright (C) 2004-2011 Brian Paul All Rights Reserved. + * Copyright (C) 2009-2011 VMware, Inc. All Rights Reserved. + * Copyright © 2010-2011 Intel Corporation + * Copyright (C) 2011 Vincent Lejeune. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * API implementation of GL_ARB_uniform_buffer_object. + * + * Every uniform (in an UBO or not) is given an index that identifies it when + * calling one of these functions. Strictly speaking, such an index doesn't + * need to match location for classic uniform ; others drivers however use this + * approach so we're also matching them for compatibility purpose. + */ + +#include <stdint.h> +#include "main/glheader.h" +#include "main/context.h" +#include "main/dispatch.h" +#include "main/image.h" +#include "main/mfeatures.h" +#include "main/mtypes.h" +#include "main/shaderapi.h" +#include "main/shaderobj.h" +#include "main/uniforms.h" +#include "program/hash_table.h" +#include "program/prog_parameter.h" +#include "program/prog_statevars.h" +#include "program/prog_instruction.h" +#include "main/ubo.h" +#include "main/bufferobj.h" + +static void +get_ubo_info(struct gl_context *ctx, + struct gl_shader_program *shProg, GLint ubo_index, + GLenum query, int *data) +{ + if (ubo_index > shProg->UBOCount || ubo_index < 0) + _mesa_error(ctx, GL_INVALID_VALUE, + "glGetActiveUniformBlock(uniformBlockIndex)"); + + struct gl_uniform_buffer_object current_ubo = + shProg->UniformBufferObject[ubo_index]; + + switch (query) { + case GL_UNIFORM_BLOCK_BINDING: + data = 0; // TODO : find a nice way to get buffer id + break; + case GL_UNIFORM_BLOCK_DATA_SIZE: + *data = current_ubo.Size; + break; + case GL_UNIFORM_BLOCK_NAME_LENGTH: + *data = strlen(current_ubo.Name) + 1; + break; + case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: + *data = current_ubo.NumberOfVariables; + break; + case GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER: + *data = current_ubo.ReferencedByVS; + break; + case GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER: + *data = current_ubo.ReferencedByGS; + break; + case GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER: + *data = current_ubo.ReferencedByFS; + break; + default: + break; + } + return; +} + +static GLuint +get_indice(const struct gl_shader_program *prog, const char *name) +{ + unsigned index; + if (prog->NamedAccessUBOVariables->get(index, name)) { + return index; + } + + return GL_INVALID_INDEX; +} + +/** + * This function checks if it can copy source into dest. + * If it cannot, it returns 1, otherwise it returns 0 and + * write size to *length if non NULL + */ +static int +copy_char_buffer(const char *source, char *dest, unsigned destsize, + int *length) +{ + unsigned sourcesize = strlen(source); + if (sourcesize > destsize + 1) { + return -1; + } + memcpy(dest, source, sourcesize); + dest[sourcesize] = '0'; + if (length) + *length = sourcesize; + return 0; +} + +static void +get_ubo_name(struct gl_context *ctx, struct gl_shader_program *shProg, + GLint index, GLsizei bufsize, GLsizei * length, char *buffer) +{ + if (index >= shProg->UBOCount || index < 0) { + _mesa_error(ctx, GL_INVALID_VALUE, + "GetActiveUniformBlockName(uniformBlockIndex)"); + return; + } + + struct gl_uniform_buffer_object current_ubo = + shProg->UniformBufferObject[index]; + + if (copy_char_buffer(current_ubo.Name, buffer, bufsize, length)) { + _mesa_error(ctx, GL_INVALID_VALUE, + "GetActiveUniformBlockName(bufSize)"); + return; + } + return; +} + +static void +get_uniform_variable_name(struct gl_context *ctx, + struct gl_shader_program *sh, GLuint index, + GLsizei bufsize, GLsizei * length, + char *uniformName) +{ + const char *name = 0; // sh->Uniforms->Uniforms[index].Name; + if (copy_char_buffer(name, uniformName, bufsize, length)) { + _mesa_error(ctx, GL_INVALID_VALUE, "GetActiveUniformName(bufSize)"); + return; + } + return; +} + +static GLint +get_uniform_variable_info(struct gl_shader_program *sh, GLuint index, + GLenum pname) +{ + uint starting_index = sh->NumUserUniformStorage; + if (index >= starting_index) { + struct gl_program_ubo_variable *var = + sh->IndexedUBOVariables[index - starting_index]; + switch (pname) { + case GL_UNIFORM_TYPE: + return var->Type; + case GL_UNIFORM_SIZE: + return 1; + case GL_UNIFORM_NAME_LENGTH: + return strlen(var->Name) + 1; + case GL_UNIFORM_BLOCK_INDEX: + return var->UBO->Index; + case GL_UNIFORM_OFFSET: + return var->Offset; + case GL_UNIFORM_ARRAY_STRIDE: + case GL_UNIFORM_MATRIX_STRIDE: + return var->Stride; + case GL_UNIFORM_IS_ROW_MAJOR: + return sh->UniformBufferObject[var->UBO->Index].MatrixLayout == + UBO_MATRIX_LAYOUT_ROW_MAJOR; + } + } + + return GL_INVALID_VALUE; +} + +void GLAPIENTRY +_mesa_GetActiveUniformBlockInfo(GLuint program, GLuint ubo_index, + GLenum pname, GLint * params) +{ + GET_CURRENT_CONTEXT(ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err(ctx, program, + "GetActiveUniformBlockiv"); + get_ubo_info(ctx, shProg, ubo_index, pname, params); +} + +void GLAPIENTRY +_mesa_GetActiveUniformBlockName(GLuint program, GLuint ubo_index, + GLsizei bufsize, GLint * length, char *name) +{ + GET_CURRENT_CONTEXT(ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err(ctx, program, + "GetActiveUniformBlockName"); + get_ubo_name(ctx, shProg, ubo_index, bufsize, length, name); +} + +void GLAPIENTRY +_mesa_GetUniformIndices(GLuint program, GLsizei number_of_variables, + const char **names, GLuint * indices) +{ + unsigned i; + GET_CURRENT_CONTEXT(ctx); + + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err(ctx, program, "GetUniformIndices"); + for (i = 0; i < number_of_variables; i++) { + indices[i] = get_indice(shProg, names[i]); + } +} + +void GLAPIENTRY +_mesa_GetActiveUniformName(GLuint program, GLuint index, GLsizei bufsize, + GLsizei * length, char *uniformName) +{ + GET_CURRENT_CONTEXT(ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err(ctx, program, "GetActiveUniformName"); + get_uniform_variable_name(ctx, shProg, index, bufsize, length, + uniformName); + +} + +void GLAPIENTRY +_mesa_GetActiveUniformsiv(GLuint program, GLsizei uniformCount, + const GLuint * uniformIndices, GLenum pname, + GLint * param) +{ + unsigned k; + GET_CURRENT_CONTEXT(ctx); + struct gl_shader_program *shProg = + _mesa_lookup_shader_program_err(ctx, program, "GetActiveUniformsiv"); + + + for (k = 0; k < uniformCount; k++) { + param[k] = get_uniform_variable_info(shProg, uniformIndices[k], pname); + } +} + +void +_mesa_init_shader_uniform_buffer_objects(struct _glapi_table *exec) +{ +/* GL_ARB_Uniform_Buffer_Object */ + SET_GetActiveUniformBlockiv(exec, _mesa_GetActiveUniformBlockInfo); + SET_GetActiveUniformBlockName(exec, _mesa_GetActiveUniformBlockName); + SET_GetUniformIndices(exec, _mesa_GetUniformIndices); + SET_GetActiveUniformName(exec, _mesa_GetActiveUniformName); + SET_GetActiveUniformsiv(exec, _mesa_GetActiveUniformsiv); +} diff --git a/src/mesa/main/ubo.h b/src/mesa/main/ubo.h new file mode 100644 index 0000000..32ea8f9 --- /dev/null +++ b/src/mesa/main/ubo.h @@ -0,0 +1,33 @@ +#ifndef UBO_H +#define UBO_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#include "glheader.h" +struct _glapi_table; + +void GLAPIENTRY +_mesa_GetActiveUniformBlockInfo (GLuint, GLuint, GLenum, GLint *); + +extern void GLAPIENTRY +_mesa_GetActiveUniformBlockName (GLuint, GLuint, GLsizei, GLint *, char *); + +extern void GLAPIENTRY +_mesa_GetUniformIndices (GLuint, GLsizei, const char **, GLuint *); + +extern void GLAPIENTRY +_mesa_GetActiveUniformName (GLuint, GLuint, GLsizei, GLsizei *, char *); + +void GLAPIENTRY +_mesa_GetActiveUniformsiv (GLuint, GLsizei, const GLuint *, GLenum, GLint *); + +void _mesa_init_shader_uniform_buffer_objects (struct _glapi_table *exec); + +#ifdef __cplusplus +} +#endif + +#endif // UBO_H diff --git a/src/mesa/sources.mak b/src/mesa/sources.mak index e72a1ce..c87b4be 100644 --- a/src/mesa/sources.mak +++ b/src/mesa/sources.mak @@ -106,6 +106,7 @@ MAIN_SOURCES = \ MAIN_CXX_SOURCES = \ main/ff_fragment_shader.cpp \ main/shader_query.cpp \ + main/ubo.cpp \ main/uniform_query.cpp MATH_SOURCES = \ diff --git a/src/mesa/state_tracker/st_cb_bufferobjects.c b/src/mesa/state_tracker/st_cb_bufferobjects.c index adac92f..c8ee6d7 100644 --- a/src/mesa/state_tracker/st_cb_bufferobjects.c +++ b/src/mesa/state_tracker/st_cb_bufferobjects.c @@ -128,7 +128,8 @@ st_bufferobj_subdata(struct gl_context *ctx, */ pipe_buffer_write(st_context(ctx)->pipe, st_obj->buffer, - offset, size, data); + offset, size, data); + } @@ -195,6 +196,9 @@ st_bufferobj_data(struct gl_context *ctx, case GL_ELEMENT_ARRAY_BUFFER_ARB: bind = PIPE_BIND_INDEX_BUFFER; break; + case GL_UNIFORM_BUFFER: + bind = PIPE_BIND_CONSTANT_BUFFER; + break; default: bind = 0; } -- 1.7.7 _______________________________________________ mesa-dev mailing list mesa-dev@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/mesa-dev