1947 lines
51 KiB
C
1947 lines
51 KiB
C
/*
|
|
* Mesa 3-D graphics library
|
|
*
|
|
* Copyright (C) 2004-2008 Brian Paul All Rights Reserved.
|
|
* Copyright (C) 2009-2010 VMware, Inc. All Rights Reserved.
|
|
*
|
|
* 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
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS 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.
|
|
*/
|
|
|
|
/**
|
|
* \file shaderapi.c
|
|
* \author Brian Paul
|
|
*
|
|
* Implementation of GLSL-related API functions.
|
|
* The glUniform* functions are in uniforms.c
|
|
*
|
|
*
|
|
* XXX things to do:
|
|
* 1. Check that the right error code is generated for all _mesa_error() calls.
|
|
* 2. Insert FLUSH_VERTICES calls in various places
|
|
*/
|
|
|
|
|
|
#include "main/glheader.h"
|
|
#include "main/context.h"
|
|
#include "main/dispatch.h"
|
|
#include "main/enums.h"
|
|
#include "main/hash.h"
|
|
#include "main/hash_table.h"
|
|
#include "main/mtypes.h"
|
|
#include "main/shaderapi.h"
|
|
#include "main/shaderobj.h"
|
|
#include "main/transformfeedback.h"
|
|
#include "main/uniforms.h"
|
|
#include "program/program.h"
|
|
#include "program/prog_print.h"
|
|
#include "program/prog_parameter.h"
|
|
#include "ralloc.h"
|
|
#include <stdbool.h>
|
|
#include "../glsl/glsl_parser_extras.h"
|
|
#include "../glsl/ir.h"
|
|
#include "../glsl/ir_uniform.h"
|
|
#include "../glsl/program.h"
|
|
|
|
/** Define this to enable shader substitution (see below) */
|
|
#define SHADER_SUBST 0
|
|
|
|
|
|
/**
|
|
* Return mask of GLSL_x flags by examining the MESA_GLSL env var.
|
|
*/
|
|
GLbitfield
|
|
_mesa_get_shader_flags(void)
|
|
{
|
|
GLbitfield flags = 0x0;
|
|
const char *env = _mesa_getenv("MESA_GLSL");
|
|
|
|
if (env) {
|
|
if (strstr(env, "dump_on_error"))
|
|
flags |= GLSL_DUMP_ON_ERROR;
|
|
else if (strstr(env, "dump"))
|
|
flags |= GLSL_DUMP;
|
|
if (strstr(env, "log"))
|
|
flags |= GLSL_LOG;
|
|
if (strstr(env, "nopvert"))
|
|
flags |= GLSL_NOP_VERT;
|
|
if (strstr(env, "nopfrag"))
|
|
flags |= GLSL_NOP_FRAG;
|
|
if (strstr(env, "nopt"))
|
|
flags |= GLSL_NO_OPT;
|
|
else if (strstr(env, "opt"))
|
|
flags |= GLSL_OPT;
|
|
if (strstr(env, "uniform"))
|
|
flags |= GLSL_UNIFORMS;
|
|
if (strstr(env, "useprog"))
|
|
flags |= GLSL_USE_PROG;
|
|
if (strstr(env, "errors"))
|
|
flags |= GLSL_REPORT_ERRORS;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize context's shader state.
|
|
*/
|
|
void
|
|
_mesa_init_shader_state(struct gl_context *ctx)
|
|
{
|
|
/* Device drivers may override these to control what kind of instructions
|
|
* are generated by the GLSL compiler.
|
|
*/
|
|
struct gl_shader_compiler_options options;
|
|
gl_shader_stage sh;
|
|
|
|
memset(&options, 0, sizeof(options));
|
|
options.MaxUnrollIterations = 32;
|
|
options.MaxIfDepth = UINT_MAX;
|
|
|
|
/* Default pragma settings */
|
|
options.DefaultPragmas.Optimize = GL_TRUE;
|
|
|
|
for (sh = 0; sh < MESA_SHADER_STAGES; ++sh)
|
|
memcpy(&ctx->ShaderCompilerOptions[sh], &options, sizeof(options));
|
|
|
|
ctx->Shader.Flags = _mesa_get_shader_flags();
|
|
|
|
/* Extended for ARB_separate_shader_objects */
|
|
ctx->Shader.RefCount = 1;
|
|
mtx_init(&ctx->Shader.Mutex, mtx_plain);
|
|
}
|
|
|
|
|
|
/**
|
|
* Free the per-context shader-related state.
|
|
*/
|
|
void
|
|
_mesa_free_shader_state(struct gl_context *ctx)
|
|
{
|
|
int i;
|
|
for (i = 0; i < MESA_SHADER_STAGES; i++) {
|
|
_mesa_reference_shader_program(ctx, &ctx->Shader.CurrentProgram[i],
|
|
NULL);
|
|
}
|
|
_mesa_reference_shader_program(ctx, &ctx->Shader._CurrentFragmentProgram,
|
|
NULL);
|
|
_mesa_reference_shader_program(ctx, &ctx->Shader.ActiveProgram, NULL);
|
|
|
|
/* Extended for ARB_separate_shader_objects */
|
|
assert(ctx->Shader.RefCount == 1);
|
|
mtx_destroy(&ctx->Shader.Mutex);
|
|
}
|
|
|
|
|
|
/**
|
|
* Copy string from <src> to <dst>, up to maxLength characters, returning
|
|
* length of <dst> in <length>.
|
|
* \param src the strings source
|
|
* \param maxLength max chars to copy
|
|
* \param length returns number of chars copied
|
|
* \param dst the string destination
|
|
*/
|
|
void
|
|
_mesa_copy_string(GLchar *dst, GLsizei maxLength,
|
|
GLsizei *length, const GLchar *src)
|
|
{
|
|
GLsizei len;
|
|
for (len = 0; len < maxLength - 1 && src && src[len]; len++)
|
|
dst[len] = src[len];
|
|
if (maxLength > 0)
|
|
dst[len] = 0;
|
|
if (length)
|
|
*length = len;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Confirm that the a shader type is valid and supported by the implementation
|
|
*
|
|
* \param ctx Current GL context
|
|
* \param type Shader target
|
|
*
|
|
*/
|
|
bool
|
|
_mesa_validate_shader_target(const struct gl_context *ctx, GLenum type)
|
|
{
|
|
/* Note: when building built-in GLSL functions, this function may be
|
|
* invoked with ctx == NULL. In that case, we can only validate that it's
|
|
* a shader target we recognize, not that it's supported in the current
|
|
* context. But that's fine--we don't need any further validation than
|
|
* that when building built-in GLSL functions.
|
|
*/
|
|
|
|
switch (type) {
|
|
case GL_FRAGMENT_SHADER:
|
|
return ctx == NULL || ctx->Extensions.ARB_fragment_shader;
|
|
case GL_VERTEX_SHADER:
|
|
return ctx == NULL || ctx->Extensions.ARB_vertex_shader;
|
|
case GL_GEOMETRY_SHADER_ARB:
|
|
return ctx == NULL || _mesa_has_geometry_shaders(ctx);
|
|
case GL_COMPUTE_SHADER:
|
|
return ctx == NULL || ctx->Extensions.ARB_compute_shader;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
static GLboolean
|
|
is_program(struct gl_context *ctx, GLuint name)
|
|
{
|
|
struct gl_shader_program *shProg = _mesa_lookup_shader_program(ctx, name);
|
|
return shProg ? GL_TRUE : GL_FALSE;
|
|
}
|
|
|
|
|
|
static GLboolean
|
|
is_shader(struct gl_context *ctx, GLuint name)
|
|
{
|
|
struct gl_shader *shader = _mesa_lookup_shader(ctx, name);
|
|
return shader ? GL_TRUE : GL_FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Attach shader to a shader program.
|
|
*/
|
|
static void
|
|
attach_shader(struct gl_context *ctx, GLuint program, GLuint shader)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
struct gl_shader *sh;
|
|
GLuint i, n;
|
|
|
|
const bool same_type_disallowed = _mesa_is_gles(ctx);
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glAttachShader");
|
|
if (!shProg)
|
|
return;
|
|
|
|
sh = _mesa_lookup_shader_err(ctx, shader, "glAttachShader");
|
|
if (!sh) {
|
|
return;
|
|
}
|
|
|
|
n = shProg->NumShaders;
|
|
for (i = 0; i < n; i++) {
|
|
if (shProg->Shaders[i] == sh) {
|
|
/* The shader is already attched to this program. The
|
|
* GL_ARB_shader_objects spec says:
|
|
*
|
|
* "The error INVALID_OPERATION is generated by AttachObjectARB
|
|
* if <obj> is already attached to <containerObj>."
|
|
*/
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, "glAttachShader");
|
|
return;
|
|
} else if (same_type_disallowed &&
|
|
shProg->Shaders[i]->Type == sh->Type) {
|
|
/* Shader with the same type is already attached to this program,
|
|
* OpenGL ES 2.0 and 3.0 specs say:
|
|
*
|
|
* "Multiple shader objects of the same type may not be attached
|
|
* to a single program object. [...] The error INVALID_OPERATION
|
|
* is generated if [...] another shader object of the same type
|
|
* as shader is already attached to program."
|
|
*/
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, "glAttachShader");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* grow list */
|
|
shProg->Shaders = (struct gl_shader **)
|
|
_mesa_realloc(shProg->Shaders,
|
|
n * sizeof(struct gl_shader *),
|
|
(n + 1) * sizeof(struct gl_shader *));
|
|
if (!shProg->Shaders) {
|
|
_mesa_error(ctx, GL_OUT_OF_MEMORY, "glAttachShader");
|
|
return;
|
|
}
|
|
|
|
/* append */
|
|
shProg->Shaders[n] = NULL; /* since realloc() didn't zero the new space */
|
|
_mesa_reference_shader(ctx, &shProg->Shaders[n], sh);
|
|
shProg->NumShaders++;
|
|
}
|
|
|
|
|
|
static GLuint
|
|
create_shader(struct gl_context *ctx, GLenum type)
|
|
{
|
|
struct gl_shader *sh;
|
|
GLuint name;
|
|
|
|
if (!_mesa_validate_shader_target(ctx, type)) {
|
|
_mesa_error(ctx, GL_INVALID_ENUM, "CreateShader(type)");
|
|
return 0;
|
|
}
|
|
|
|
name = _mesa_HashFindFreeKeyBlock(ctx->Shared->ShaderObjects, 1);
|
|
sh = ctx->Driver.NewShader(ctx, name, type);
|
|
_mesa_HashInsert(ctx->Shared->ShaderObjects, name, sh);
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
static GLuint
|
|
create_shader_program(struct gl_context *ctx)
|
|
{
|
|
GLuint name;
|
|
struct gl_shader_program *shProg;
|
|
|
|
name = _mesa_HashFindFreeKeyBlock(ctx->Shared->ShaderObjects, 1);
|
|
|
|
shProg = ctx->Driver.NewShaderProgram(ctx, name);
|
|
|
|
_mesa_HashInsert(ctx->Shared->ShaderObjects, name, shProg);
|
|
|
|
assert(shProg->RefCount == 1);
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
/**
|
|
* Named w/ "2" to indicate OpenGL 2.x vs GL_ARB_fragment_programs's
|
|
* DeleteProgramARB.
|
|
*/
|
|
static void
|
|
delete_shader_program(struct gl_context *ctx, GLuint name)
|
|
{
|
|
/*
|
|
* NOTE: deleting shaders/programs works a bit differently than
|
|
* texture objects (and buffer objects, etc). Shader/program
|
|
* handles/IDs exist in the hash table until the object is really
|
|
* deleted (refcount==0). With texture objects, the handle/ID is
|
|
* removed from the hash table in glDeleteTextures() while the tex
|
|
* object itself might linger until its refcount goes to zero.
|
|
*/
|
|
struct gl_shader_program *shProg;
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, name, "glDeleteProgram");
|
|
if (!shProg)
|
|
return;
|
|
|
|
if (!shProg->DeletePending) {
|
|
shProg->DeletePending = GL_TRUE;
|
|
|
|
/* effectively, decr shProg's refcount */
|
|
_mesa_reference_shader_program(ctx, &shProg, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
delete_shader(struct gl_context *ctx, GLuint shader)
|
|
{
|
|
struct gl_shader *sh;
|
|
|
|
sh = _mesa_lookup_shader_err(ctx, shader, "glDeleteShader");
|
|
if (!sh)
|
|
return;
|
|
|
|
if (!sh->DeletePending) {
|
|
sh->DeletePending = GL_TRUE;
|
|
|
|
/* effectively, decr sh's refcount */
|
|
_mesa_reference_shader(ctx, &sh, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
detach_shader(struct gl_context *ctx, GLuint program, GLuint shader)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
GLuint n;
|
|
GLuint i, j;
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glDetachShader");
|
|
if (!shProg)
|
|
return;
|
|
|
|
n = shProg->NumShaders;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (shProg->Shaders[i]->Name == shader) {
|
|
/* found it */
|
|
struct gl_shader **newList;
|
|
|
|
/* release */
|
|
_mesa_reference_shader(ctx, &shProg->Shaders[i], NULL);
|
|
|
|
/* alloc new, smaller array */
|
|
newList = malloc((n - 1) * sizeof(struct gl_shader *));
|
|
if (!newList) {
|
|
_mesa_error(ctx, GL_OUT_OF_MEMORY, "glDetachShader");
|
|
return;
|
|
}
|
|
/* Copy old list entries to new list, skipping removed entry at [i] */
|
|
for (j = 0; j < i; j++) {
|
|
newList[j] = shProg->Shaders[j];
|
|
}
|
|
while (++i < n) {
|
|
newList[j++] = shProg->Shaders[i];
|
|
}
|
|
|
|
/* Free old list and install new one */
|
|
free(shProg->Shaders);
|
|
shProg->Shaders = newList;
|
|
shProg->NumShaders = n - 1;
|
|
|
|
#ifdef DEBUG
|
|
/* sanity check - make sure the new list's entries are sensible */
|
|
for (j = 0; j < shProg->NumShaders; j++) {
|
|
assert(shProg->Shaders[j]->Type == GL_VERTEX_SHADER ||
|
|
shProg->Shaders[j]->Type == GL_GEOMETRY_SHADER ||
|
|
shProg->Shaders[j]->Type == GL_FRAGMENT_SHADER);
|
|
assert(shProg->Shaders[j]->RefCount > 0);
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* not found */
|
|
{
|
|
GLenum err;
|
|
if (is_shader(ctx, shader))
|
|
err = GL_INVALID_OPERATION;
|
|
else if (is_program(ctx, shader))
|
|
err = GL_INVALID_OPERATION;
|
|
else
|
|
err = GL_INVALID_VALUE;
|
|
_mesa_error(ctx, err, "glDetachShader(shader)");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Return list of shaders attached to shader program.
|
|
*/
|
|
static void
|
|
get_attached_shaders(struct gl_context *ctx, GLuint program, GLsizei maxCount,
|
|
GLsizei *count, GLuint *obj)
|
|
{
|
|
struct gl_shader_program *shProg =
|
|
_mesa_lookup_shader_program_err(ctx, program, "glGetAttachedShaders");
|
|
if (shProg) {
|
|
GLuint i;
|
|
for (i = 0; i < (GLuint) maxCount && i < shProg->NumShaders; i++) {
|
|
obj[i] = shProg->Shaders[i]->Name;
|
|
}
|
|
if (count)
|
|
*count = i;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* glGetHandleARB() - return ID/name of currently bound shader program.
|
|
*/
|
|
static GLuint
|
|
get_handle(struct gl_context *ctx, GLenum pname)
|
|
{
|
|
if (pname == GL_PROGRAM_OBJECT_ARB) {
|
|
if (ctx->Shader.ActiveProgram)
|
|
return ctx->Shader.ActiveProgram->Name;
|
|
else
|
|
return 0;
|
|
}
|
|
else {
|
|
_mesa_error(ctx, GL_INVALID_ENUM, "glGetHandleARB");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a geometry shader query is valid at this time. If not, report an
|
|
* error and return false.
|
|
*
|
|
* From GL 3.2 section 6.1.16 (Shader and Program Queries):
|
|
*
|
|
* "If GEOMETRY_VERTICES_OUT, GEOMETRY_INPUT_TYPE, or GEOMETRY_OUTPUT_TYPE
|
|
* are queried for a program which has not been linked successfully, or
|
|
* which does not contain objects to form a geometry shader, then an
|
|
* INVALID_OPERATION error is generated."
|
|
*/
|
|
static bool
|
|
check_gs_query(struct gl_context *ctx, const struct gl_shader_program *shProg)
|
|
{
|
|
if (shProg->LinkStatus &&
|
|
shProg->_LinkedShaders[MESA_SHADER_GEOMETRY] != NULL) {
|
|
return true;
|
|
}
|
|
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glGetProgramv(linked geometry shader required)");
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* glGetProgramiv() - get shader program state.
|
|
* Note that this is for GLSL shader programs, not ARB vertex/fragment
|
|
* programs (see glGetProgramivARB).
|
|
*/
|
|
static void
|
|
get_programiv(struct gl_context *ctx, GLuint program, GLenum pname, GLint *params)
|
|
{
|
|
struct gl_shader_program *shProg
|
|
= _mesa_lookup_shader_program(ctx, program);
|
|
|
|
/* Is transform feedback available in this context?
|
|
*/
|
|
const bool has_xfb =
|
|
(ctx->API == API_OPENGL_COMPAT && ctx->Extensions.EXT_transform_feedback)
|
|
|| ctx->API == API_OPENGL_CORE
|
|
|| _mesa_is_gles3(ctx);
|
|
|
|
/* True if geometry shaders (of the form that was adopted into GLSL 1.50
|
|
* and GL 3.2) are available in this context
|
|
*/
|
|
const bool has_core_gs = _mesa_is_desktop_gl(ctx) && ctx->Version >= 32;
|
|
|
|
/* Are uniform buffer objects available in this context?
|
|
*/
|
|
const bool has_ubo =
|
|
(ctx->API == API_OPENGL_COMPAT && ctx->Extensions.ARB_uniform_buffer_object)
|
|
|| ctx->API == API_OPENGL_CORE
|
|
|| _mesa_is_gles3(ctx);
|
|
|
|
if (!shProg) {
|
|
_mesa_error(ctx, GL_INVALID_VALUE, "glGetProgramiv(program)");
|
|
return;
|
|
}
|
|
|
|
switch (pname) {
|
|
case GL_DELETE_STATUS:
|
|
*params = shProg->DeletePending;
|
|
return;
|
|
case GL_LINK_STATUS:
|
|
*params = shProg->LinkStatus;
|
|
return;
|
|
case GL_VALIDATE_STATUS:
|
|
*params = shProg->Validated;
|
|
return;
|
|
case GL_INFO_LOG_LENGTH:
|
|
*params = shProg->InfoLog ? strlen(shProg->InfoLog) + 1 : 0;
|
|
return;
|
|
case GL_ATTACHED_SHADERS:
|
|
*params = shProg->NumShaders;
|
|
return;
|
|
case GL_ACTIVE_ATTRIBUTES:
|
|
*params = _mesa_count_active_attribs(shProg);
|
|
return;
|
|
case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
|
|
*params = _mesa_longest_attribute_name_length(shProg);
|
|
return;
|
|
case GL_ACTIVE_UNIFORMS:
|
|
*params = shProg->NumUserUniformStorage;
|
|
return;
|
|
case GL_ACTIVE_UNIFORM_MAX_LENGTH: {
|
|
unsigned i;
|
|
GLint max_len = 0;
|
|
|
|
for (i = 0; i < shProg->NumUserUniformStorage; i++) {
|
|
/* Add one for the terminating NUL character for a non-array, and
|
|
* 4 for the "[0]" and the NUL for an array.
|
|
*/
|
|
const GLint len = strlen(shProg->UniformStorage[i].name) + 1 +
|
|
((shProg->UniformStorage[i].array_elements != 0) ? 3 : 0);
|
|
|
|
if (len > max_len)
|
|
max_len = len;
|
|
}
|
|
|
|
*params = max_len;
|
|
return;
|
|
}
|
|
case GL_TRANSFORM_FEEDBACK_VARYINGS:
|
|
if (!has_xfb)
|
|
break;
|
|
*params = shProg->TransformFeedback.NumVarying;
|
|
return;
|
|
case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH: {
|
|
unsigned i;
|
|
GLint max_len = 0;
|
|
if (!has_xfb)
|
|
break;
|
|
|
|
for (i = 0; i < shProg->TransformFeedback.NumVarying; i++) {
|
|
/* Add one for the terminating NUL character.
|
|
*/
|
|
const GLint len = strlen(shProg->TransformFeedback.VaryingNames[i]) + 1;
|
|
|
|
if (len > max_len)
|
|
max_len = len;
|
|
}
|
|
|
|
*params = max_len;
|
|
return;
|
|
}
|
|
case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
|
|
if (!has_xfb)
|
|
break;
|
|
*params = shProg->TransformFeedback.BufferMode;
|
|
return;
|
|
case GL_GEOMETRY_VERTICES_OUT:
|
|
if (!has_core_gs)
|
|
break;
|
|
if (check_gs_query(ctx, shProg))
|
|
*params = shProg->Geom.VerticesOut;
|
|
return;
|
|
case GL_GEOMETRY_SHADER_INVOCATIONS:
|
|
if (!has_core_gs || !ctx->Extensions.ARB_gpu_shader5)
|
|
break;
|
|
if (check_gs_query(ctx, shProg))
|
|
*params = shProg->Geom.Invocations;
|
|
return;
|
|
case GL_GEOMETRY_INPUT_TYPE:
|
|
if (!has_core_gs)
|
|
break;
|
|
if (check_gs_query(ctx, shProg))
|
|
*params = shProg->Geom.InputType;
|
|
return;
|
|
case GL_GEOMETRY_OUTPUT_TYPE:
|
|
if (!has_core_gs)
|
|
break;
|
|
if (check_gs_query(ctx, shProg))
|
|
*params = shProg->Geom.OutputType;
|
|
return;
|
|
case GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH: {
|
|
unsigned i;
|
|
GLint max_len = 0;
|
|
|
|
if (!has_ubo)
|
|
break;
|
|
|
|
for (i = 0; i < shProg->NumUniformBlocks; i++) {
|
|
/* Add one for the terminating NUL character.
|
|
*/
|
|
const GLint len = strlen(shProg->UniformBlocks[i].Name) + 1;
|
|
|
|
if (len > max_len)
|
|
max_len = len;
|
|
}
|
|
|
|
*params = max_len;
|
|
return;
|
|
}
|
|
case GL_ACTIVE_UNIFORM_BLOCKS:
|
|
if (!has_ubo)
|
|
break;
|
|
|
|
*params = shProg->NumUniformBlocks;
|
|
return;
|
|
case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
|
|
/* This enum isn't part of the OES extension for OpenGL ES 2.0. It is
|
|
* only available with desktop OpenGL 3.0+ with the
|
|
* GL_ARB_get_program_binary extension or OpenGL ES 3.0.
|
|
*
|
|
* On desktop, we ignore the 3.0+ requirement because it is silly.
|
|
*/
|
|
if (!_mesa_is_desktop_gl(ctx) && !_mesa_is_gles3(ctx))
|
|
break;
|
|
|
|
*params = shProg->BinaryRetreivableHint;
|
|
return;
|
|
case GL_PROGRAM_BINARY_LENGTH:
|
|
*params = 0;
|
|
return;
|
|
case GL_ACTIVE_ATOMIC_COUNTER_BUFFERS:
|
|
if (!ctx->Extensions.ARB_shader_atomic_counters)
|
|
break;
|
|
|
|
*params = shProg->NumAtomicBuffers;
|
|
return;
|
|
case GL_COMPUTE_WORK_GROUP_SIZE: {
|
|
int i;
|
|
if (!_mesa_is_desktop_gl(ctx) || !ctx->Extensions.ARB_compute_shader)
|
|
break;
|
|
if (!shProg->LinkStatus) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, "glGetProgramiv(program not "
|
|
"linked)");
|
|
return;
|
|
}
|
|
if (shProg->_LinkedShaders[MESA_SHADER_COMPUTE] == NULL) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, "glGetProgramiv(no compute "
|
|
"shaders)");
|
|
return;
|
|
}
|
|
for (i = 0; i < 3; i++)
|
|
params[i] = shProg->Comp.LocalSize[i];
|
|
return;
|
|
}
|
|
case GL_PROGRAM_SEPARABLE:
|
|
if (!ctx->Extensions.ARB_separate_shader_objects)
|
|
break;
|
|
|
|
*params = shProg->SeparateShader;
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_mesa_error(ctx, GL_INVALID_ENUM, "glGetProgramiv(pname=%s)",
|
|
_mesa_lookup_enum_by_nr(pname));
|
|
}
|
|
|
|
|
|
/**
|
|
* glGetShaderiv() - get GLSL shader state
|
|
*/
|
|
static void
|
|
get_shaderiv(struct gl_context *ctx, GLuint name, GLenum pname, GLint *params)
|
|
{
|
|
struct gl_shader *shader =
|
|
_mesa_lookup_shader_err(ctx, name, "glGetShaderiv");
|
|
|
|
if (!shader) {
|
|
return;
|
|
}
|
|
|
|
switch (pname) {
|
|
case GL_SHADER_TYPE:
|
|
*params = shader->Type;
|
|
break;
|
|
case GL_DELETE_STATUS:
|
|
*params = shader->DeletePending;
|
|
break;
|
|
case GL_COMPILE_STATUS:
|
|
*params = shader->CompileStatus;
|
|
break;
|
|
case GL_INFO_LOG_LENGTH:
|
|
*params = shader->InfoLog ? strlen(shader->InfoLog) + 1 : 0;
|
|
break;
|
|
case GL_SHADER_SOURCE_LENGTH:
|
|
*params = shader->Source ? strlen((char *) shader->Source) + 1 : 0;
|
|
break;
|
|
default:
|
|
_mesa_error(ctx, GL_INVALID_ENUM, "glGetShaderiv(pname)");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
get_program_info_log(struct gl_context *ctx, GLuint program, GLsizei bufSize,
|
|
GLsizei *length, GLchar *infoLog)
|
|
{
|
|
struct gl_shader_program *shProg
|
|
= _mesa_lookup_shader_program(ctx, program);
|
|
if (!shProg) {
|
|
_mesa_error(ctx, GL_INVALID_VALUE, "glGetProgramInfoLog(program)");
|
|
return;
|
|
}
|
|
_mesa_copy_string(infoLog, bufSize, length, shProg->InfoLog);
|
|
}
|
|
|
|
|
|
static void
|
|
get_shader_info_log(struct gl_context *ctx, GLuint shader, GLsizei bufSize,
|
|
GLsizei *length, GLchar *infoLog)
|
|
{
|
|
struct gl_shader *sh = _mesa_lookup_shader(ctx, shader);
|
|
if (!sh) {
|
|
_mesa_error(ctx, GL_INVALID_VALUE, "glGetShaderInfoLog(shader)");
|
|
return;
|
|
}
|
|
_mesa_copy_string(infoLog, bufSize, length, sh->InfoLog);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return shader source code.
|
|
*/
|
|
static void
|
|
get_shader_source(struct gl_context *ctx, GLuint shader, GLsizei maxLength,
|
|
GLsizei *length, GLchar *sourceOut)
|
|
{
|
|
struct gl_shader *sh;
|
|
sh = _mesa_lookup_shader_err(ctx, shader, "glGetShaderSource");
|
|
if (!sh) {
|
|
return;
|
|
}
|
|
_mesa_copy_string(sourceOut, maxLength, length, sh->Source);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set/replace shader source code. A helper function used by
|
|
* glShaderSource[ARB] and glCreateShaderProgramEXT.
|
|
*/
|
|
static void
|
|
shader_source(struct gl_context *ctx, GLuint shader, const GLchar *source)
|
|
{
|
|
struct gl_shader *sh;
|
|
|
|
sh = _mesa_lookup_shader_err(ctx, shader, "glShaderSource");
|
|
if (!sh)
|
|
return;
|
|
|
|
/* free old shader source string and install new one */
|
|
free((void *)sh->Source);
|
|
sh->Source = source;
|
|
sh->CompileStatus = GL_FALSE;
|
|
#ifdef DEBUG
|
|
sh->SourceChecksum = _mesa_str_checksum(sh->Source);
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Compile a shader.
|
|
*/
|
|
static void
|
|
compile_shader(struct gl_context *ctx, GLuint shaderObj)
|
|
{
|
|
struct gl_shader *sh;
|
|
struct gl_shader_compiler_options *options;
|
|
|
|
sh = _mesa_lookup_shader_err(ctx, shaderObj, "glCompileShader");
|
|
if (!sh)
|
|
return;
|
|
|
|
options = &ctx->ShaderCompilerOptions[sh->Stage];
|
|
|
|
/* set default pragma state for shader */
|
|
sh->Pragmas = options->DefaultPragmas;
|
|
|
|
if (!sh->Source) {
|
|
/* If the user called glCompileShader without first calling
|
|
* glShaderSource, we should fail to compile, but not raise a GL_ERROR.
|
|
*/
|
|
sh->CompileStatus = GL_FALSE;
|
|
} else {
|
|
if (ctx->Shader.Flags & GLSL_DUMP) {
|
|
fprintf(stderr, "GLSL source for %s shader %d:\n",
|
|
_mesa_shader_stage_to_string(sh->Stage), sh->Name);
|
|
fprintf(stderr, "%s\n", sh->Source);
|
|
fflush(stderr);
|
|
}
|
|
|
|
/* this call will set the shader->CompileStatus field to indicate if
|
|
* compilation was successful.
|
|
*/
|
|
_mesa_glsl_compile_shader(ctx, sh, false, false);
|
|
|
|
if (ctx->Shader.Flags & GLSL_LOG) {
|
|
_mesa_write_shader_to_file(sh);
|
|
}
|
|
|
|
if (ctx->Shader.Flags & GLSL_DUMP) {
|
|
if (sh->CompileStatus) {
|
|
fprintf(stderr, "GLSL IR for shader %d:\n", sh->Name);
|
|
_mesa_print_ir(stderr, sh->ir, NULL);
|
|
fprintf(stderr, "\n\n");
|
|
} else {
|
|
fprintf(stderr, "GLSL shader %d failed to compile.\n", sh->Name);
|
|
}
|
|
if (sh->InfoLog && sh->InfoLog[0] != 0) {
|
|
fprintf(stderr, "GLSL shader %d info log:\n", sh->Name);
|
|
fprintf(stderr, "%s\n", sh->InfoLog);
|
|
}
|
|
fflush(stderr);
|
|
}
|
|
|
|
}
|
|
|
|
if (!sh->CompileStatus) {
|
|
if (ctx->Shader.Flags & GLSL_DUMP_ON_ERROR) {
|
|
fprintf(stderr, "GLSL source for %s shader %d:\n",
|
|
_mesa_shader_stage_to_string(sh->Stage), sh->Name);
|
|
fprintf(stderr, "%s\n", sh->Source);
|
|
fprintf(stderr, "Info Log:\n%s\n", sh->InfoLog);
|
|
fflush(stderr);
|
|
}
|
|
|
|
if (ctx->Shader.Flags & GLSL_REPORT_ERRORS) {
|
|
_mesa_debug(ctx, "Error compiling shader %u:\n%s\n",
|
|
sh->Name, sh->InfoLog);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Link a program's shaders.
|
|
*/
|
|
static void
|
|
link_program(struct gl_context *ctx, GLuint program)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glLinkProgram");
|
|
if (!shProg)
|
|
return;
|
|
|
|
/* From the ARB_transform_feedback2 specification:
|
|
* "The error INVALID_OPERATION is generated by LinkProgram if <program> is
|
|
* the name of a program being used by one or more transform feedback
|
|
* objects, even if the objects are not currently bound or are paused."
|
|
*/
|
|
if (_mesa_transform_feedback_is_using_program(ctx, shProg)) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glLinkProgram(transform feedback is using the program)");
|
|
return;
|
|
}
|
|
|
|
FLUSH_VERTICES(ctx, _NEW_PROGRAM);
|
|
|
|
_mesa_glsl_link_shader(ctx, shProg);
|
|
|
|
if (shProg->LinkStatus == GL_FALSE &&
|
|
(ctx->Shader.Flags & GLSL_REPORT_ERRORS)) {
|
|
_mesa_debug(ctx, "Error linking program %u:\n%s\n",
|
|
shProg->Name, shProg->InfoLog);
|
|
}
|
|
|
|
/* debug code */
|
|
if (0) {
|
|
GLuint i;
|
|
|
|
printf("Link %u shaders in program %u: %s\n",
|
|
shProg->NumShaders, shProg->Name,
|
|
shProg->LinkStatus ? "Success" : "Failed");
|
|
|
|
for (i = 0; i < shProg->NumShaders; i++) {
|
|
printf(" shader %u, type 0x%x\n",
|
|
shProg->Shaders[i]->Name,
|
|
shProg->Shaders[i]->Type);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Print basic shader info (for debug).
|
|
*/
|
|
static void
|
|
print_shader_info(const struct gl_shader_program *shProg)
|
|
{
|
|
GLuint i;
|
|
|
|
printf("Mesa: glUseProgram(%u)\n", shProg->Name);
|
|
for (i = 0; i < shProg->NumShaders; i++) {
|
|
printf(" %s shader %u, checksum %u\n",
|
|
_mesa_shader_stage_to_string(shProg->Shaders[i]->Stage),
|
|
shProg->Shaders[i]->Name,
|
|
shProg->Shaders[i]->SourceChecksum);
|
|
}
|
|
if (shProg->_LinkedShaders[MESA_SHADER_VERTEX])
|
|
printf(" vert prog %u\n",
|
|
shProg->_LinkedShaders[MESA_SHADER_VERTEX]->Program->Id);
|
|
if (shProg->_LinkedShaders[MESA_SHADER_FRAGMENT])
|
|
printf(" frag prog %u\n",
|
|
shProg->_LinkedShaders[MESA_SHADER_FRAGMENT]->Program->Id);
|
|
if (shProg->_LinkedShaders[MESA_SHADER_GEOMETRY])
|
|
printf(" geom prog %u\n",
|
|
shProg->_LinkedShaders[MESA_SHADER_GEOMETRY]->Program->Id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Use the named shader program for subsequent glUniform calls
|
|
*/
|
|
void
|
|
_mesa_active_program(struct gl_context *ctx, struct gl_shader_program *shProg,
|
|
const char *caller)
|
|
{
|
|
if ((shProg != NULL) && !shProg->LinkStatus) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"%s(program %u not linked)", caller, shProg->Name);
|
|
return;
|
|
}
|
|
|
|
if (ctx->Shader.ActiveProgram != shProg) {
|
|
_mesa_reference_shader_program(ctx, &ctx->Shader.ActiveProgram, shProg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
static void
|
|
use_shader_program(struct gl_context *ctx, GLenum type,
|
|
struct gl_shader_program *shProg)
|
|
{
|
|
struct gl_shader_program **target;
|
|
gl_shader_stage stage = _mesa_shader_enum_to_shader_stage(type);
|
|
|
|
target = &ctx->Shader.CurrentProgram[stage];
|
|
if ((shProg == NULL) || (shProg->_LinkedShaders[stage] == NULL))
|
|
shProg = NULL;
|
|
|
|
if (*target != shProg) {
|
|
FLUSH_VERTICES(ctx, _NEW_PROGRAM | _NEW_PROGRAM_CONSTANTS);
|
|
|
|
/* If the shader is also bound as the current rendering shader, unbind
|
|
* it from that binding point as well. This ensures that the correct
|
|
* semantics of glDeleteProgram are maintained.
|
|
*/
|
|
switch (type) {
|
|
case GL_VERTEX_SHADER:
|
|
/* Empty for now. */
|
|
break;
|
|
case GL_GEOMETRY_SHADER_ARB:
|
|
/* Empty for now. */
|
|
break;
|
|
case GL_COMPUTE_SHADER:
|
|
/* Empty for now. */
|
|
break;
|
|
case GL_FRAGMENT_SHADER:
|
|
if (*target == ctx->Shader._CurrentFragmentProgram) {
|
|
_mesa_reference_shader_program(ctx,
|
|
&ctx->Shader._CurrentFragmentProgram,
|
|
NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
_mesa_reference_shader_program(ctx, target, shProg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use the named shader program for subsequent rendering.
|
|
*/
|
|
void
|
|
_mesa_use_program(struct gl_context *ctx, struct gl_shader_program *shProg)
|
|
{
|
|
use_shader_program(ctx, GL_VERTEX_SHADER, shProg);
|
|
use_shader_program(ctx, GL_GEOMETRY_SHADER_ARB, shProg);
|
|
use_shader_program(ctx, GL_FRAGMENT_SHADER, shProg);
|
|
use_shader_program(ctx, GL_COMPUTE_SHADER, shProg);
|
|
_mesa_active_program(ctx, shProg, "glUseProgram");
|
|
|
|
if (ctx->Driver.UseProgram)
|
|
ctx->Driver.UseProgram(ctx, shProg);
|
|
}
|
|
|
|
|
|
/**
|
|
* Do validation of the given shader program.
|
|
* \param errMsg returns error message if validation fails.
|
|
* \return GL_TRUE if valid, GL_FALSE if invalid (and set errMsg)
|
|
*/
|
|
static GLboolean
|
|
validate_shader_program(const struct gl_shader_program *shProg,
|
|
char *errMsg)
|
|
{
|
|
if (!shProg->LinkStatus) {
|
|
return GL_FALSE;
|
|
}
|
|
|
|
/* From the GL spec, a program is invalid if any of these are true:
|
|
|
|
any two active samplers in the current program object are of
|
|
different types, but refer to the same texture image unit,
|
|
|
|
any active sampler in the current program object refers to a texture
|
|
image unit where fixed-function fragment processing accesses a
|
|
texture target that does not match the sampler type, or
|
|
|
|
the sum of the number of active samplers in the program and the
|
|
number of texture image units enabled for fixed-function fragment
|
|
processing exceeds the combined limit on the total number of texture
|
|
image units allowed.
|
|
*/
|
|
|
|
|
|
/*
|
|
* Check: any two active samplers in the current program object are of
|
|
* different types, but refer to the same texture image unit,
|
|
*/
|
|
if (!_mesa_sampler_uniforms_are_valid(shProg, errMsg, 100))
|
|
return GL_FALSE;
|
|
|
|
return GL_TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Called via glValidateProgram()
|
|
*/
|
|
static void
|
|
validate_program(struct gl_context *ctx, GLuint program)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
char errMsg[100] = "";
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glValidateProgram");
|
|
if (!shProg) {
|
|
return;
|
|
}
|
|
|
|
shProg->Validated = validate_shader_program(shProg, errMsg);
|
|
if (!shProg->Validated) {
|
|
/* update info log */
|
|
if (shProg->InfoLog) {
|
|
ralloc_free(shProg->InfoLog);
|
|
}
|
|
shProg->InfoLog = ralloc_strdup(shProg, errMsg);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_AttachObjectARB(GLhandleARB program, GLhandleARB shader)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
attach_shader(ctx, program, shader);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_AttachShader(GLuint program, GLuint shader)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
attach_shader(ctx, program, shader);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_CompileShader(GLhandleARB shaderObj)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
if (MESA_VERBOSE & VERBOSE_API)
|
|
_mesa_debug(ctx, "glCompileShader %u\n", shaderObj);
|
|
compile_shader(ctx, shaderObj);
|
|
}
|
|
|
|
|
|
GLuint GLAPIENTRY
|
|
_mesa_CreateShader(GLenum type)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
if (MESA_VERBOSE & VERBOSE_API)
|
|
_mesa_debug(ctx, "glCreateShader %s\n", _mesa_lookup_enum_by_nr(type));
|
|
return create_shader(ctx, type);
|
|
}
|
|
|
|
|
|
GLhandleARB GLAPIENTRY
|
|
_mesa_CreateShaderObjectARB(GLenum type)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
return create_shader(ctx, type);
|
|
}
|
|
|
|
|
|
GLuint GLAPIENTRY
|
|
_mesa_CreateProgram(void)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
if (MESA_VERBOSE & VERBOSE_API)
|
|
_mesa_debug(ctx, "glCreateProgram\n");
|
|
return create_shader_program(ctx);
|
|
}
|
|
|
|
|
|
GLhandleARB GLAPIENTRY
|
|
_mesa_CreateProgramObjectARB(void)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
return create_shader_program(ctx);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_DeleteObjectARB(GLhandleARB obj)
|
|
{
|
|
if (MESA_VERBOSE & VERBOSE_API) {
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
_mesa_debug(ctx, "glDeleteObjectARB(%u)\n", obj);
|
|
}
|
|
|
|
if (obj) {
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
FLUSH_VERTICES(ctx, 0);
|
|
if (is_program(ctx, obj)) {
|
|
delete_shader_program(ctx, obj);
|
|
}
|
|
else if (is_shader(ctx, obj)) {
|
|
delete_shader(ctx, obj);
|
|
}
|
|
else {
|
|
/* error? */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_DeleteProgram(GLuint name)
|
|
{
|
|
if (name) {
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
FLUSH_VERTICES(ctx, 0);
|
|
delete_shader_program(ctx, name);
|
|
}
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_DeleteShader(GLuint name)
|
|
{
|
|
if (name) {
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
FLUSH_VERTICES(ctx, 0);
|
|
delete_shader(ctx, name);
|
|
}
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_DetachObjectARB(GLhandleARB program, GLhandleARB shader)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
detach_shader(ctx, program, shader);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_DetachShader(GLuint program, GLuint shader)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
detach_shader(ctx, program, shader);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetAttachedObjectsARB(GLhandleARB container, GLsizei maxCount,
|
|
GLsizei * count, GLhandleARB * obj)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_attached_shaders(ctx, container, maxCount, count, obj);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetAttachedShaders(GLuint program, GLsizei maxCount,
|
|
GLsizei *count, GLuint *obj)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_attached_shaders(ctx, program, maxCount, count, obj);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetInfoLogARB(GLhandleARB object, GLsizei maxLength, GLsizei * length,
|
|
GLcharARB * infoLog)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
if (is_program(ctx, object)) {
|
|
get_program_info_log(ctx, object, maxLength, length, infoLog);
|
|
}
|
|
else if (is_shader(ctx, object)) {
|
|
get_shader_info_log(ctx, object, maxLength, length, infoLog);
|
|
}
|
|
else {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, "glGetInfoLogARB");
|
|
}
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetObjectParameterivARB(GLhandleARB object, GLenum pname, GLint *params)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
/* Implement in terms of GetProgramiv, GetShaderiv */
|
|
if (is_program(ctx, object)) {
|
|
if (pname == GL_OBJECT_TYPE_ARB) {
|
|
*params = GL_PROGRAM_OBJECT_ARB;
|
|
}
|
|
else {
|
|
get_programiv(ctx, object, pname, params);
|
|
}
|
|
}
|
|
else if (is_shader(ctx, object)) {
|
|
if (pname == GL_OBJECT_TYPE_ARB) {
|
|
*params = GL_SHADER_OBJECT_ARB;
|
|
}
|
|
else {
|
|
get_shaderiv(ctx, object, pname, params);
|
|
}
|
|
}
|
|
else {
|
|
_mesa_error(ctx, GL_INVALID_VALUE, "glGetObjectParameterivARB");
|
|
}
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetObjectParameterfvARB(GLhandleARB object, GLenum pname,
|
|
GLfloat *params)
|
|
{
|
|
GLint iparams[1]; /* XXX is one element enough? */
|
|
_mesa_GetObjectParameterivARB(object, pname, iparams);
|
|
params[0] = (GLfloat) iparams[0];
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetProgramiv(GLuint program, GLenum pname, GLint *params)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_programiv(ctx, program, pname, params);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetShaderiv(GLuint shader, GLenum pname, GLint *params)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_shaderiv(ctx, shader, pname, params);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetProgramInfoLog(GLuint program, GLsizei bufSize,
|
|
GLsizei *length, GLchar *infoLog)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_program_info_log(ctx, program, bufSize, length, infoLog);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetShaderInfoLog(GLuint shader, GLsizei bufSize,
|
|
GLsizei *length, GLchar *infoLog)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_shader_info_log(ctx, shader, bufSize, length, infoLog);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetShaderSource(GLhandleARB shader, GLsizei maxLength,
|
|
GLsizei *length, GLcharARB *sourceOut)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
get_shader_source(ctx, shader, maxLength, length, sourceOut);
|
|
}
|
|
|
|
|
|
GLhandleARB GLAPIENTRY
|
|
_mesa_GetHandleARB(GLenum pname)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
return get_handle(ctx, pname);
|
|
}
|
|
|
|
|
|
GLboolean GLAPIENTRY
|
|
_mesa_IsProgram(GLuint name)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
return is_program(ctx, name);
|
|
}
|
|
|
|
|
|
GLboolean GLAPIENTRY
|
|
_mesa_IsShader(GLuint name)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
return is_shader(ctx, name);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_LinkProgram(GLhandleARB programObj)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
link_program(ctx, programObj);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Read shader source code from a file.
|
|
* Useful for debugging to override an app's shader.
|
|
*/
|
|
static GLcharARB *
|
|
read_shader(const char *fname)
|
|
{
|
|
const int max = 50*1000;
|
|
FILE *f = fopen(fname, "r");
|
|
GLcharARB *buffer, *shader;
|
|
int len;
|
|
|
|
if (!f) {
|
|
return NULL;
|
|
}
|
|
|
|
buffer = malloc(max);
|
|
len = fread(buffer, 1, max, f);
|
|
buffer[len] = 0;
|
|
|
|
fclose(f);
|
|
|
|
shader = _mesa_strdup(buffer);
|
|
free(buffer);
|
|
|
|
return shader;
|
|
}
|
|
|
|
|
|
/**
|
|
* Called via glShaderSource() and glShaderSourceARB() API functions.
|
|
* Basically, concatenate the source code strings into one long string
|
|
* and pass it to _mesa_shader_source().
|
|
*/
|
|
void GLAPIENTRY
|
|
_mesa_ShaderSource(GLhandleARB shaderObj, GLsizei count,
|
|
const GLcharARB * const * string, const GLint * length)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
GLint *offsets;
|
|
GLsizei i, totalLength;
|
|
GLcharARB *source;
|
|
GLuint checksum;
|
|
|
|
if (!shaderObj || string == NULL) {
|
|
_mesa_error(ctx, GL_INVALID_VALUE, "glShaderSourceARB");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This array holds offsets of where the appropriate string ends, thus the
|
|
* last element will be set to the total length of the source code.
|
|
*/
|
|
offsets = malloc(count * sizeof(GLint));
|
|
if (offsets == NULL) {
|
|
_mesa_error(ctx, GL_OUT_OF_MEMORY, "glShaderSourceARB");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (string[i] == NULL) {
|
|
free((GLvoid *) offsets);
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glShaderSourceARB(null string)");
|
|
return;
|
|
}
|
|
if (length == NULL || length[i] < 0)
|
|
offsets[i] = strlen(string[i]);
|
|
else
|
|
offsets[i] = length[i];
|
|
/* accumulate string lengths */
|
|
if (i > 0)
|
|
offsets[i] += offsets[i - 1];
|
|
}
|
|
|
|
/* Total length of source string is sum off all strings plus two.
|
|
* One extra byte for terminating zero, another extra byte to silence
|
|
* valgrind warnings in the parser/grammer code.
|
|
*/
|
|
totalLength = offsets[count - 1] + 2;
|
|
source = malloc(totalLength * sizeof(GLcharARB));
|
|
if (source == NULL) {
|
|
free((GLvoid *) offsets);
|
|
_mesa_error(ctx, GL_OUT_OF_MEMORY, "glShaderSourceARB");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
GLint start = (i > 0) ? offsets[i - 1] : 0;
|
|
memcpy(source + start, string[i],
|
|
(offsets[i] - start) * sizeof(GLcharARB));
|
|
}
|
|
source[totalLength - 1] = '\0';
|
|
source[totalLength - 2] = '\0';
|
|
|
|
if (SHADER_SUBST) {
|
|
/* Compute the shader's source code checksum then try to open a file
|
|
* named newshader_<CHECKSUM>. If it exists, use it in place of the
|
|
* original shader source code. For debugging.
|
|
*/
|
|
char filename[100];
|
|
GLcharARB *newSource;
|
|
|
|
checksum = _mesa_str_checksum(source);
|
|
|
|
_mesa_snprintf(filename, sizeof(filename), "newshader_%d", checksum);
|
|
|
|
newSource = read_shader(filename);
|
|
if (newSource) {
|
|
fprintf(stderr, "Mesa: Replacing shader %u chksum=%d with %s\n",
|
|
shaderObj, checksum, filename);
|
|
free(source);
|
|
source = newSource;
|
|
}
|
|
}
|
|
|
|
shader_source(ctx, shaderObj, source);
|
|
|
|
if (SHADER_SUBST) {
|
|
struct gl_shader *sh = _mesa_lookup_shader(ctx, shaderObj);
|
|
if (sh)
|
|
sh->SourceChecksum = checksum; /* save original checksum */
|
|
}
|
|
|
|
free(offsets);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_UseProgram(GLhandleARB program)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
struct gl_shader_program *shProg;
|
|
|
|
if (_mesa_is_xfb_active_and_unpaused(ctx)) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glUseProgram(transform feedback active)");
|
|
return;
|
|
}
|
|
|
|
if (program) {
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glUseProgram");
|
|
if (!shProg) {
|
|
return;
|
|
}
|
|
if (!shProg->LinkStatus) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glUseProgram(program %u not linked)", program);
|
|
return;
|
|
}
|
|
|
|
/* debug code */
|
|
if (ctx->Shader.Flags & GLSL_USE_PROG) {
|
|
print_shader_info(shProg);
|
|
}
|
|
}
|
|
else {
|
|
shProg = NULL;
|
|
}
|
|
|
|
_mesa_use_program(ctx, shProg);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_ValidateProgram(GLhandleARB program)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
validate_program(ctx, program);
|
|
}
|
|
|
|
|
|
/**
|
|
* For OpenGL ES 2.0, GL_ARB_ES2_compatibility
|
|
*/
|
|
void GLAPIENTRY
|
|
_mesa_GetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype,
|
|
GLint* range, GLint* precision)
|
|
{
|
|
const struct gl_program_constants *limits;
|
|
const struct gl_precision *p;
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
|
|
switch (shadertype) {
|
|
case GL_VERTEX_SHADER:
|
|
limits = &ctx->Const.Program[MESA_SHADER_VERTEX];
|
|
break;
|
|
case GL_FRAGMENT_SHADER:
|
|
limits = &ctx->Const.Program[MESA_SHADER_FRAGMENT];
|
|
break;
|
|
default:
|
|
_mesa_error(ctx, GL_INVALID_ENUM,
|
|
"glGetShaderPrecisionFormat(shadertype)");
|
|
return;
|
|
}
|
|
|
|
switch (precisiontype) {
|
|
case GL_LOW_FLOAT:
|
|
p = &limits->LowFloat;
|
|
break;
|
|
case GL_MEDIUM_FLOAT:
|
|
p = &limits->MediumFloat;
|
|
break;
|
|
case GL_HIGH_FLOAT:
|
|
p = &limits->HighFloat;
|
|
break;
|
|
case GL_LOW_INT:
|
|
p = &limits->LowInt;
|
|
break;
|
|
case GL_MEDIUM_INT:
|
|
p = &limits->MediumInt;
|
|
break;
|
|
case GL_HIGH_INT:
|
|
p = &limits->HighInt;
|
|
break;
|
|
default:
|
|
_mesa_error(ctx, GL_INVALID_ENUM,
|
|
"glGetShaderPrecisionFormat(precisiontype)");
|
|
return;
|
|
}
|
|
|
|
range[0] = p->RangeMin;
|
|
range[1] = p->RangeMax;
|
|
precision[0] = p->Precision;
|
|
}
|
|
|
|
|
|
/**
|
|
* For OpenGL ES 2.0, GL_ARB_ES2_compatibility
|
|
*/
|
|
void GLAPIENTRY
|
|
_mesa_ReleaseShaderCompiler(void)
|
|
{
|
|
_mesa_destroy_shader_compiler_caches();
|
|
}
|
|
|
|
|
|
/**
|
|
* For OpenGL ES 2.0, GL_ARB_ES2_compatibility
|
|
*/
|
|
void GLAPIENTRY
|
|
_mesa_ShaderBinary(GLint n, const GLuint* shaders, GLenum binaryformat,
|
|
const void* binary, GLint length)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
(void) n;
|
|
(void) shaders;
|
|
(void) binaryformat;
|
|
(void) binary;
|
|
(void) length;
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, __FUNCTION__);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei *length,
|
|
GLenum *binaryFormat, GLvoid *binary)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glGetProgramBinary");
|
|
if (!shProg)
|
|
return;
|
|
|
|
if (!shProg->LinkStatus) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glGetProgramBinary(program %u not linked)",
|
|
shProg->Name);
|
|
return;
|
|
}
|
|
|
|
if (bufSize < 0){
|
|
_mesa_error(ctx, GL_INVALID_VALUE, "glGetProgramBinary(bufSize < 0)");
|
|
return;
|
|
}
|
|
|
|
/* The ARB_get_program_binary spec says:
|
|
*
|
|
* "If <length> is NULL, then no length is returned."
|
|
*/
|
|
if (length != NULL)
|
|
*length = 0;
|
|
|
|
(void) binaryFormat;
|
|
(void) binary;
|
|
}
|
|
|
|
void GLAPIENTRY
|
|
_mesa_ProgramBinary(GLuint program, GLenum binaryFormat,
|
|
const GLvoid *binary, GLsizei length)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program, "glProgramBinary");
|
|
if (!shProg)
|
|
return;
|
|
|
|
(void) binaryFormat;
|
|
(void) binary;
|
|
(void) length;
|
|
_mesa_error(ctx, GL_INVALID_OPERATION, __FUNCTION__);
|
|
}
|
|
|
|
|
|
void GLAPIENTRY
|
|
_mesa_ProgramParameteri(GLuint program, GLenum pname, GLint value)
|
|
{
|
|
struct gl_shader_program *shProg;
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program,
|
|
"glProgramParameteri");
|
|
if (!shProg)
|
|
return;
|
|
|
|
switch (pname) {
|
|
case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
|
|
/* This enum isn't part of the OES extension for OpenGL ES 2.0, but it
|
|
* is part of OpenGL ES 3.0. For the ES2 case, this function shouldn't
|
|
* even be in the dispatch table, so we shouldn't need to expclicitly
|
|
* check here.
|
|
*
|
|
* On desktop, we ignore the 3.0+ requirement because it is silly.
|
|
*/
|
|
|
|
/* The ARB_get_program_binary extension spec says:
|
|
*
|
|
* "An INVALID_VALUE error is generated if the <value> argument to
|
|
* ProgramParameteri is not TRUE or FALSE."
|
|
*/
|
|
if (value != GL_TRUE && value != GL_FALSE) {
|
|
_mesa_error(ctx, GL_INVALID_VALUE,
|
|
"glProgramParameteri(pname=%s, value=%d): "
|
|
"value must be 0 or 1.",
|
|
_mesa_lookup_enum_by_nr(pname),
|
|
value);
|
|
return;
|
|
}
|
|
|
|
/* No need to notify the driver. Any changes will actually take effect
|
|
* the next time the shader is linked.
|
|
*
|
|
* The ARB_get_program_binary extension spec says:
|
|
*
|
|
* "To indicate that a program binary is likely to be retrieved,
|
|
* ProgramParameteri should be called with <pname>
|
|
* PROGRAM_BINARY_RETRIEVABLE_HINT and <value> TRUE. This setting
|
|
* will not be in effect until the next time LinkProgram or
|
|
* ProgramBinary has been called successfully."
|
|
*
|
|
* The resloution of issue 9 in the extension spec also says:
|
|
*
|
|
* "The application may use the PROGRAM_BINARY_RETRIEVABLE_HINT hint
|
|
* to indicate to the GL implementation that this program will
|
|
* likely be saved with GetProgramBinary at some point. This will
|
|
* give the GL implementation the opportunity to track any state
|
|
* changes made to the program before being saved such that when it
|
|
* is loaded again a recompile can be avoided."
|
|
*/
|
|
shProg->BinaryRetreivableHint = value;
|
|
return;
|
|
|
|
case GL_PROGRAM_SEPARABLE:
|
|
if (!ctx->Extensions.ARB_separate_shader_objects)
|
|
break;
|
|
|
|
/* Spec imply that the behavior is the same as ARB_get_program_binary
|
|
* Chapter 7.3 Program Objects
|
|
*/
|
|
if (value != GL_TRUE && value != GL_FALSE) {
|
|
_mesa_error(ctx, GL_INVALID_VALUE,
|
|
"glProgramParameteri(pname=%s, value=%d): "
|
|
"value must be 0 or 1.",
|
|
_mesa_lookup_enum_by_nr(pname),
|
|
value);
|
|
return;
|
|
}
|
|
shProg->SeparateShader = value;
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_mesa_error(ctx, GL_INVALID_ENUM, "glProgramParameteri(pname=%s)",
|
|
_mesa_lookup_enum_by_nr(pname));
|
|
}
|
|
|
|
void
|
|
_mesa_use_shader_program(struct gl_context *ctx, GLenum type,
|
|
struct gl_shader_program *shProg)
|
|
{
|
|
use_shader_program(ctx, type, shProg);
|
|
|
|
if (ctx->Driver.UseProgram)
|
|
ctx->Driver.UseProgram(ctx, shProg);
|
|
}
|
|
|
|
|
|
/**
|
|
* For GL_EXT_separate_shader_objects
|
|
*/
|
|
void GLAPIENTRY
|
|
_mesa_UseShaderProgramEXT(GLenum type, GLuint program)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
struct gl_shader_program *shProg = NULL;
|
|
|
|
if (!_mesa_validate_shader_target(ctx, type)) {
|
|
_mesa_error(ctx, GL_INVALID_ENUM, "glUseShaderProgramEXT(type)");
|
|
return;
|
|
}
|
|
|
|
if (_mesa_is_xfb_active_and_unpaused(ctx)) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glUseShaderProgramEXT(transform feedback is active)");
|
|
return;
|
|
}
|
|
|
|
if (program) {
|
|
shProg = _mesa_lookup_shader_program_err(ctx, program,
|
|
"glUseShaderProgramEXT");
|
|
if (shProg == NULL)
|
|
return;
|
|
|
|
if (!shProg->LinkStatus) {
|
|
_mesa_error(ctx, GL_INVALID_OPERATION,
|
|
"glUseShaderProgramEXT(program not linked)");
|
|
return;
|
|
}
|
|
}
|
|
|
|
_mesa_use_shader_program(ctx, type, shProg);
|
|
}
|
|
|
|
|
|
/**
|
|
* For GL_EXT_separate_shader_objects
|
|
*/
|
|
void GLAPIENTRY
|
|
_mesa_ActiveProgramEXT(GLuint program)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
struct gl_shader_program *shProg = (program != 0)
|
|
? _mesa_lookup_shader_program_err(ctx, program, "glActiveProgramEXT")
|
|
: NULL;
|
|
|
|
_mesa_active_program(ctx, shProg, "glActiveProgramEXT");
|
|
return;
|
|
}
|
|
|
|
static GLuint
|
|
_mesa_create_shader_program(struct gl_context* ctx, GLboolean separate,
|
|
GLenum type, GLsizei count, const GLchar* const *strings)
|
|
{
|
|
const GLuint shader = create_shader(ctx, type);
|
|
GLuint program = 0;
|
|
|
|
if (shader) {
|
|
_mesa_ShaderSource(shader, count, strings, NULL);
|
|
|
|
compile_shader(ctx, shader);
|
|
|
|
program = create_shader_program(ctx);
|
|
if (program) {
|
|
struct gl_shader_program *shProg;
|
|
struct gl_shader *sh;
|
|
GLint compiled = GL_FALSE;
|
|
|
|
shProg = _mesa_lookup_shader_program(ctx, program);
|
|
sh = _mesa_lookup_shader(ctx, shader);
|
|
|
|
shProg->SeparateShader = separate;
|
|
|
|
get_shaderiv(ctx, shader, GL_COMPILE_STATUS, &compiled);
|
|
if (compiled) {
|
|
attach_shader(ctx, program, shader);
|
|
link_program(ctx, program);
|
|
detach_shader(ctx, program, shader);
|
|
|
|
#if 0
|
|
/* Possibly... */
|
|
if (active-user-defined-varyings-in-linked-program) {
|
|
append-error-to-info-log;
|
|
shProg->LinkStatus = GL_FALSE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ralloc_strcat(&shProg->InfoLog, sh->InfoLog);
|
|
}
|
|
|
|
delete_shader(ctx, shader);
|
|
}
|
|
|
|
return program;
|
|
}
|
|
|
|
|
|
/**
|
|
* Copy program-specific data generated by linking from the gl_shader_program
|
|
* object to a specific gl_program object.
|
|
*/
|
|
void
|
|
_mesa_copy_linked_program_data(gl_shader_stage type,
|
|
const struct gl_shader_program *src,
|
|
struct gl_program *dst)
|
|
{
|
|
switch (type) {
|
|
case MESA_SHADER_VERTEX:
|
|
dst->UsesClipDistanceOut = src->Vert.UsesClipDistance;
|
|
break;
|
|
case MESA_SHADER_GEOMETRY: {
|
|
struct gl_geometry_program *dst_gp = (struct gl_geometry_program *) dst;
|
|
dst_gp->VerticesIn = src->Geom.VerticesIn;
|
|
dst_gp->VerticesOut = src->Geom.VerticesOut;
|
|
dst_gp->Invocations = src->Geom.Invocations;
|
|
dst_gp->InputType = src->Geom.InputType;
|
|
dst_gp->OutputType = src->Geom.OutputType;
|
|
dst->UsesClipDistanceOut = src->Geom.UsesClipDistance;
|
|
dst_gp->UsesEndPrimitive = src->Geom.UsesEndPrimitive;
|
|
}
|
|
break;
|
|
case MESA_SHADER_COMPUTE: {
|
|
struct gl_compute_program *dst_cp = (struct gl_compute_program *) dst;
|
|
int i;
|
|
for (i = 0; i < 3; i++)
|
|
dst_cp->LocalSize[i] = src->Comp.LocalSize[i];
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* For GL_EXT_separate_shader_objects
|
|
*/
|
|
GLuint GLAPIENTRY
|
|
_mesa_CreateShaderProgramEXT(GLenum type, const GLchar *string)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
|
|
return _mesa_create_shader_program(ctx, GL_FALSE, type, 1, &string);
|
|
}
|
|
|
|
/**
|
|
* ARB_separate_shader_objects: Compile & Link Program
|
|
*
|
|
* Basically the same as _mesa_CreateShaderProgramEXT but with support of
|
|
* multiple strings and sets the SeparateShader flag to true.
|
|
*/
|
|
GLuint GLAPIENTRY
|
|
_mesa_CreateShaderProgramv(GLenum type, GLsizei count,
|
|
const GLchar* const *strings)
|
|
{
|
|
GET_CURRENT_CONTEXT(ctx);
|
|
|
|
return _mesa_create_shader_program(ctx, GL_TRUE, type, count, strings);
|
|
}
|