Merge "Adding freetype font rendering to renderscript."

This commit is contained in:
Alex Sakhartchouk
2010-06-25 09:24:19 -07:00
committed by Android (Google) Code Review
22 changed files with 1250 additions and 15 deletions

View File

@@ -386,6 +386,21 @@ public class Allocation extends BaseObj {
Bitmap b = BitmapFactory.decodeResource(res, id, mBitmapOptions);
return createFromBitmapBoxed(rs, b, dstFmt, genMips);
}
static public Allocation createFromString(RenderScript rs, String str)
throws IllegalArgumentException {
byte[] allocArray = null;
try {
allocArray = str.getBytes("UTF-8");
Allocation alloc = Allocation.createSized(rs, Element.U8(rs), allocArray.length);
alloc.data(allocArray);
return alloc;
}
catch (Exception e) {
Log.e("rs", "could not convert string to utf-8");
}
return null;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.renderscript;
import java.io.IOException;
import java.io.InputStream;
import android.content.res.Resources;
import android.content.res.AssetManager;
import android.util.Log;
import android.util.TypedValue;
/**
* @hide
*
**/
public class Font extends BaseObj {
Font(int id, RenderScript rs) {
super(rs);
mID = id;
}
static public Font create(RenderScript rs, Resources res, String fileName, int size)
throws IllegalArgumentException {
rs.validate();
try {
int dpi = res.getDisplayMetrics().densityDpi;
int fontId = rs.nFontCreateFromFile(fileName, size, dpi);
if(fontId == 0) {
throw new IllegalStateException("Load loading a font");
}
Font rsFont = new Font(fontId, rs);
return rsFont;
} catch (Exception e) {
// Ignore
}
return null;
}
}

View File

@@ -123,6 +123,8 @@ public class RenderScript {
native void nFileA3DGetIndexEntries(int fileA3D, int numEntries, int[] IDs, String[] names);
native int nFileA3DGetEntryByIndex(int fileA3D, int index);
native int nFontCreateFromFile(String fileName, int size, int dpi);
native void nAdapter1DBindAllocation(int ad, int alloc);
native void nAdapter1DSetConstraint(int ad, int dim, int value);
native void nAdapter1DData(int ad, int[] d);

View File

@@ -762,6 +762,19 @@ nFileA3DGetEntryByIndex(JNIEnv *_env, jobject _this, jint fileA3D, jint index)
return id;
}
// -----------------------------------
static int
nFontCreateFromFile(JNIEnv *_env, jobject _this, jstring fileName, jint fontSize, jint dpi)
{
RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
const char* fileNameUTF = _env->GetStringUTFChars(fileName, NULL);
jint id = (jint)rsFontCreateFromFile(con, fileNameUTF, fontSize, dpi);
return id;
}
// -----------------------------------
static void
@@ -1395,6 +1408,12 @@ static JNINativeMethod methods[] = {
{"nContextDeinitToClient", "()V", (void*)nContextDeinitToClient },
{"nFileOpen", "([B)I", (void*)nFileOpen },
{"nFileA3DCreateFromAssetStream", "(I)I", (void*)nFileA3DCreateFromAssetStream },
{"nFileA3DGetNumIndexEntries", "(I)I", (void*)nFileA3DGetNumIndexEntries },
{"nFileA3DGetIndexEntries", "(II[I[Ljava/lang/String;)V", (void*)nFileA3DGetIndexEntries },
{"nFileA3DGetEntryByIndex", "(II)I", (void*)nFileA3DGetEntryByIndex },
{"nFontCreateFromFile", "(Ljava/lang/String;II)I", (void*)nFontCreateFromFile },
{"nElementCreate", "(IIZI)I", (void*)nElementCreate },
{"nElementCreate2", "([I[Ljava/lang/String;)I", (void*)nElementCreate2 },
@@ -1494,11 +1513,6 @@ static JNINativeMethod methods[] = {
{"nSimpleMeshBindVertex", "(III)V", (void*)nSimpleMeshBindVertex },
{"nSimpleMeshBindIndex", "(II)V", (void*)nSimpleMeshBindIndex },
{"nFileA3DCreateFromAssetStream", "(I)I", (void*)nFileA3DCreateFromAssetStream },
{"nFileA3DGetNumIndexEntries", "(I)I", (void*)nFileA3DGetNumIndexEntries },
{"nFileA3DGetIndexEntries", "(II[I[Ljava/lang/String;)V", (void*)nFileA3DGetIndexEntries },
{"nFileA3DGetEntryByIndex", "(II)I", (void*)nFileA3DGetEntryByIndex },
};
static int registerFuncs(JNIEnv *_env)

View File

@@ -81,13 +81,14 @@ LOCAL_SRC_FILES:= \
rsContext.cpp \
rsDevice.cpp \
rsElement.cpp \
rsFileA3D.cpp \
rsFileA3D.cpp \
rsFont.cpp \
rsLight.cpp \
rsLocklessFifo.cpp \
rsObjectBase.cpp \
rsMatrix.cpp \
rsMesh.cpp \
rsMutex.cpp \
rsMesh.cpp \
rsMutex.cpp \
rsProgram.cpp \
rsProgramFragment.cpp \
rsProgramStore.cpp \
@@ -99,16 +100,21 @@ LOCAL_SRC_FILES:= \
rsScriptC_Lib.cpp \
rsScriptC_LibCL.cpp \
rsScriptC_LibGL.cpp \
rsShaderCache.cpp \
rsShaderCache.cpp \
rsSignal.cpp \
rsSimpleMesh.cpp \
rsStream.cpp \
rsStream.cpp \
rsThreadIO.cpp \
rsType.cpp \
rsVertexArray.cpp
LOCAL_SHARED_LIBRARIES += libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libbcc
LOCAL_STATIC_LIBRARIES := libft2
LOCAL_C_INCLUDES += external/freetype/include
LOCAL_LDLIBS := -lpthread -ldl
LOCAL_MODULE:= libRS
LOCAL_MODULE_TAGS := optional

View File

@@ -35,6 +35,7 @@ typedef void * RsContext;
typedef void * RsDevice;
typedef void * RsElement;
typedef void * RsFile;
typedef void * RsFont;
typedef void * RsSampler;
typedef void * RsScript;
typedef void * RsSimpleMesh;

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.modelviewer;
import android.renderscript.*;
import android.content.res.Resources;
import android.util.Log;
public class ScriptC_Modelviewer extends ScriptC {
// Constructor
public ScriptC_Modelviewer(RenderScript rs, Resources resources, int id, boolean isRoot) {
super(rs, resources, id, isRoot);
}
private final static int mExportVarIdx_gPVBackground = 0;
private ProgramVertex mExportVar_gPVBackground;
public void set_gPVBackground(ProgramVertex v) {
mExportVar_gPVBackground = v;
setVar(mExportVarIdx_gPVBackground, (v == null) ? 0 : v.getID());
}
public ProgramVertex get_gPVBackground() {
return mExportVar_gPVBackground;
}
private final static int mExportVarIdx_gPFBackground = 1;
private ProgramFragment mExportVar_gPFBackground;
public void set_gPFBackground(ProgramFragment v) {
mExportVar_gPFBackground = v;
setVar(mExportVarIdx_gPFBackground, (v == null) ? 0 : v.getID());
}
public ProgramFragment get_gPFBackground() {
return mExportVar_gPFBackground;
}
private final static int mExportVarIdx_gTGrid = 2;
private Allocation mExportVar_gTGrid;
public void set_gTGrid(Allocation v) {
mExportVar_gTGrid = v;
setVar(mExportVarIdx_gTGrid, (v == null) ? 0 : v.getID());
}
public Allocation get_gTGrid() {
return mExportVar_gTGrid;
}
private final static int mExportVarIdx_gTestMesh = 3;
private SimpleMesh mExportVar_gTestMesh;
public void set_gTestMesh(SimpleMesh v) {
mExportVar_gTestMesh = v;
setVar(mExportVarIdx_gTestMesh, (v == null) ? 0 : v.getID());
}
public SimpleMesh get_gTestMesh() {
return mExportVar_gTestMesh;
}
private final static int mExportVarIdx_gPFSBackground = 4;
private ProgramStore mExportVar_gPFSBackground;
public void set_gPFSBackground(ProgramStore v) {
mExportVar_gPFSBackground = v;
setVar(mExportVarIdx_gPFSBackground, (v == null) ? 0 : v.getID());
}
public ProgramStore get_gPFSBackground() {
return mExportVar_gPFSBackground;
}
private final static int mExportVarIdx_gRotate = 5;
private float mExportVar_gRotate;
public void set_gRotate(float v) {
mExportVar_gRotate = v;
setVar(mExportVarIdx_gRotate, v);
}
public float get_gRotate() {
return mExportVar_gRotate;
}
private final static int mExportVarIdx_gItalic = 6;
private Font mExportVar_gItalic;
public void set_gItalic(Font v) {
mExportVar_gItalic = v;
setVar(mExportVarIdx_gItalic, v);
}
public Font get_gItalic() {
return mExportVar_gItalic;
}
private final static int mExportVarIdx_gTextAlloc = 7;
private Allocation mExportVar_gTextAlloc;
public void set_gTextAlloc(Allocation v) {
mExportVar_gTextAlloc = v;
setVar(mExportVarIdx_gTextAlloc, (v == null) ? 0 : v.getID());
}
public Allocation get_gTextAlloc() {
return mExportVar_gTextAlloc;
}
}

View File

@@ -14,6 +14,8 @@
#pragma version(1)
#pragma rs java_package_name(com.android.modelviewer)
#include "../../../../scriptc/rs_types.rsh"
#include "../../../../scriptc/rs_math.rsh"
#include "../../../../scriptc/rs_graphics.rsh"
@@ -28,7 +30,13 @@ rs_program_store gPFSBackground;
float gRotate;
#pragma rs export_var(gPVBackground, gPFBackground, gTGrid, gTestMesh, gPFSBackground, gRotate)
rs_font gItalic;
rs_allocation gTextAlloc;
#pragma rs export_var(gPVBackground, gPFBackground, gTGrid, gTestMesh, gPFSBackground, gRotate, gItalic, gTextAlloc)
float gDT;
int64_t gLastTime;
void init() {
gRotate = 0.0f;
@@ -50,10 +58,17 @@ int root(int launchID) {
// Position our model on the screen
rsMatrixTranslate(&matrix, 0.0f, -0.3f, 1.2f);
rsMatrixScale(&matrix, 0.2f, 0.2f, 0.2f);
rsMatrixRotate(&matrix, -25.0f, 1.0f, 0.0f, 0.0f);
rsMatrixRotate(&matrix, gRotate, 0.0f, 1.0f, 0.0f);
rsgProgramVertexLoadModelMatrix(&matrix);
rsgDrawSimpleMesh(gTestMesh);
color(0.3f, 0.3f, 0.3f, 1.0f);
rsgDrawText("Renderscript model test", 30, 695);
rsgBindFont(gItalic);
rsgDrawText(gTextAlloc, 30, 730);
return 10;
}

View File

@@ -57,6 +57,9 @@ public class ModelViewerRS {
private SimpleMesh mMesh;
private Font mItalic;
private Allocation mTextAlloc;
private ScriptC_Modelviewer mScript;
int mLastX;
@@ -123,11 +126,17 @@ public class ModelViewerRS {
private void loadImage() {
mGridImage = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.robot, Element.RGB_565(mRS), true);
mGridImage.uploadToTexture(1);
mGridImage.uploadToTexture(0);
mScript.set_gTGrid(mGridImage);
}
private void initTextAllocation() {
String allocString = "Displaying file: R.raw.robot";
mTextAlloc = Allocation.createFromString(mRS, allocString);
mScript.set_gTextAlloc(mTextAlloc);
}
private void initRS() {
mScript = new ScriptC_Modelviewer(mRS, mRes, R.raw.modelviewer_bc, true);
@@ -148,6 +157,11 @@ public class ModelViewerRS {
mScript.set_gTestMesh(mMesh);
}
mItalic = Font.create(mRS, mRes, "DroidSerif-Italic.ttf", 10);
mScript.set_gItalic(mItalic);
initTextAllocation();
mRS.contextBindRootScript(mScript);
}
}

View File

@@ -86,7 +86,6 @@ public class ModelViewerView extends RSSurfaceView {
ret = false;
}
Log.v("rs", "Values " + (int)ev.getX() + " " + (int)ev.getY());
mRender.touchEvent((int)ev.getX(), (int)ev.getY());
return ret;
}

View File

@@ -92,5 +92,27 @@ public class ScriptC_Modelviewer extends ScriptC {
return mExportVar_gRotate;
}
private final static int mExportVarIdx_gItalic = 6;
private Font mExportVar_gItalic;
public void set_gItalic(Font v) {
mExportVar_gItalic = v;
setVar(mExportVarIdx_gItalic, (v == null) ? 0 : v.getID());
}
public Font get_gItalic() {
return mExportVar_gItalic;
}
private final static int mExportVarIdx_gTextAlloc = 7;
private Allocation mExportVar_gTextAlloc;
public void set_gTextAlloc(Allocation v) {
mExportVar_gTextAlloc = v;
setVar(mExportVarIdx_gTextAlloc, (v == null) ? 0 : v.getID());
}
public Allocation get_gTextAlloc() {
return mExportVar_gTextAlloc;
}
}

View File

@@ -23,6 +23,10 @@ ContextBindProgramRaster {
param RsProgramRaster pgm
}
ContextBindFont {
param RsFont pgm
}
ContextPause {
}
@@ -467,6 +471,13 @@ FileA3DGetEntryByIndex {
ret RsObjectBase
}
FontCreateFromFile {
param const char *name
param uint32_t fontSize
param uint32_t dpi
ret RsFont
}
SimpleMeshCreate {
ret RsSimpleMesh
param RsAllocation prim

View File

@@ -133,6 +133,7 @@ uint32_t Context::runScript(Script *s)
ObjectBaseRef<ProgramVertex> vtx(mVertex);
ObjectBaseRef<ProgramStore> store(mFragmentStore);
ObjectBaseRef<ProgramRaster> raster(mRaster);
ObjectBaseRef<Font> font(mFont);
uint32_t ret = s->run(this);
@@ -140,6 +141,7 @@ uint32_t Context::runScript(Script *s)
mVertex.set(vtx);
mFragmentStore.set(store);
mRaster.set(raster);
mFont.set(font);
return ret;
}
@@ -290,6 +292,8 @@ void * Context::threadProc(void *vrsc)
rsc->setFragment(NULL);
rsc->mStateFragmentStore.init(rsc);
rsc->setFragmentStore(NULL);
rsc->mStateFont.init(rsc);
rsc->setFont(NULL);
rsc->mStateVertexArray.init(rsc);
}
@@ -328,11 +332,13 @@ void * Context::threadProc(void *vrsc)
rsc->mFragment.clear();
rsc->mVertex.clear();
rsc->mFragmentStore.clear();
rsc->mFont.clear();
rsc->mRootScript.clear();
rsc->mStateRaster.deinit(rsc);
rsc->mStateVertex.deinit(rsc);
rsc->mStateFragment.deinit(rsc);
rsc->mStateFragmentStore.deinit(rsc);
rsc->mStateFont.deinit(rsc);
}
ObjectBase::zeroAllUserRef(rsc);
@@ -597,6 +603,16 @@ void Context::setVertex(ProgramVertex *pv)
}
}
void Context::setFont(Font *f)
{
rsAssert(mIsGraphicsContext);
if (f == NULL) {
mFont.set(mStateFont.mDefault);
} else {
mFont.set(f);
}
}
void Context::assignName(ObjectBase *obj, const char *name, uint32_t len)
{
rsAssert(!obj->getName());
@@ -807,6 +823,13 @@ void rsi_ContextBindProgramVertex(Context *rsc, RsProgramVertex vpv)
rsc->setVertex(pv);
}
void rsi_ContextBindFont(Context *rsc, RsFont vfont)
{
Font *font = static_cast<Font *>(vfont);
rsc->setFont(font);
}
void rsi_AssignName(Context *rsc, void * obj, const char *name, uint32_t len)
{
ObjectBase *ob = static_cast<ObjectBase *>(obj);

View File

@@ -32,6 +32,7 @@
#include "rsAdapter.h"
#include "rsSampler.h"
#include "rsLight.h"
#include "rsFont.h"
#include "rsProgramFragment.h"
#include "rsProgramStore.h"
#include "rsProgramRaster.h"
@@ -76,6 +77,7 @@ public:
ProgramVertexState mStateVertex;
LightState mStateLight;
VertexArrayState mStateVertexArray;
FontState mStateFont;
ScriptCState mScriptC;
ShaderCache mShaderCache;
@@ -86,6 +88,7 @@ public:
void setVertex(ProgramVertex *);
void setFragment(ProgramFragment *);
void setFragmentStore(ProgramStore *);
void setFont(Font *);
void updateSurface(void *sur);
@@ -93,6 +96,7 @@ public:
const ProgramStore * getFragmentStore() {return mFragmentStore.get();}
const ProgramRaster * getRaster() {return mRaster.get();}
const ProgramVertex * getVertex() {return mVertex.get();}
Font * getFont() {return mFont.get();}
bool setupCheck();
bool checkDriver() const {return mEGL.mSurface != 0;}
@@ -124,6 +128,9 @@ public:
ProgramRaster * getDefaultProgramRaster() const {
return mStateRaster.mDefault.get();
}
Font* getDefaultFont() const {
return mStateFont.mDefault.get();
}
uint32_t getWidth() const {return mWidth;}
uint32_t getHeight() const {return mHeight;}
@@ -221,7 +228,7 @@ protected:
ObjectBaseRef<ProgramVertex> mVertex;
ObjectBaseRef<ProgramStore> mFragmentStore;
ObjectBaseRef<ProgramRaster> mRaster;
ObjectBaseRef<Font> mFont;
struct ObjDestroyOOB {
Mutex mMutex;

View File

@@ -26,6 +26,7 @@
#include "rsMesh.h"
#include "rsAnimation.h"
using namespace android;
using namespace android::renderscript;

689
libs/rs/rsFont.cpp Normal file
View File

@@ -0,0 +1,689 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_RS_BUILD_FOR_HOST
#include "rsContext.h"
#else
#include "rsContextHostStub.h"
#endif
#include "rsFont.h"
#include "rsProgramFragment.h"
#include FT_BITMAP_H
#include <GLES/gl.h>
#include <GLES/glext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
using namespace android;
using namespace android::renderscript;
Font::Font(Context *rsc) : ObjectBase(rsc), mCachedGlyphs(NULL)
{
mInitialized = false;
mHasKerning = false;
}
bool Font::init(const char *name, uint32_t fontSize, uint32_t dpi)
{
if(mInitialized) {
LOGE("Reinitialization of fonts not supported");
return false;
}
String8 fontsDir("/fonts/");
String8 fullPath(getenv("ANDROID_ROOT"));
fullPath += fontsDir;
fullPath += name;
FT_Error error = FT_New_Face(mRSC->mStateFont.mLibrary, fullPath.string(), 0, &mFace);
if(error) {
LOGE("Unable to initialize font %s", fullPath.string());
return false;
}
mFontName = name;
mFontSize = fontSize;
mDpi = dpi;
//LOGE("Font initialized: %s", fullPath.string());
error = FT_Set_Char_Size(mFace, fontSize * 64, 0, dpi, 0);
if(error) {
LOGE("Unable to set font size on %s", fullPath.string());
return false;
}
mHasKerning = FT_HAS_KERNING(mFace);
LOGE("Kerning: %i", mHasKerning);
mInitialized = true;
return true;
}
void Font::invalidateTextureCache()
{
for(uint32_t i = 0; i < mCachedGlyphs.size(); i ++) {
mCachedGlyphs.valueAt(i)->mIsValid = false;
}
}
void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y)
{
FontState *state = &mRSC->mStateFont;
int nPenX = x + glyph->mBitmapLeft;
int nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
state->appendMeshQuad(nPenX, nPenY, 0,
glyph->mBitmapMinU, glyph->mBitmapMaxV,
nPenX + (int)glyph->mBitmapWidth, nPenY, 0,
glyph->mBitmapMaxU, glyph->mBitmapMaxV,
nPenX + (int)glyph->mBitmapWidth, nPenY - (int)glyph->mBitmapHeight, 0,
glyph->mBitmapMaxU, glyph->mBitmapMinV,
nPenX, nPenY - (int)glyph->mBitmapHeight, 0,
glyph->mBitmapMinU, glyph->mBitmapMinV);
}
void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y)
{
if(!mInitialized || numGlyphs == 0 || text == NULL || len == 0) {
return;
}
int penX = x, penY = y;
int glyphsLeft = 1;
if(numGlyphs > 0) {
glyphsLeft = numGlyphs;
}
size_t index = start;
size_t nextIndex = 0;
while (glyphsLeft > 0) {
int32_t utfChar = utf32_at(text, len, index, &nextIndex);
// Reached the end of the string or encountered
if(utfChar < 0) {
break;
}
// Move to the next character in the array
index = nextIndex;
CachedGlyphInfo *cachedGlyph = mCachedGlyphs.valueFor((uint32_t)utfChar);
if(cachedGlyph == NULL) {
cachedGlyph = cacheGlyph((uint32_t)utfChar);
}
// Is the glyph still in texture cache?
if(!cachedGlyph->mIsValid) {
updateGlyphCache(cachedGlyph);
}
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if(cachedGlyph->mIsValid) {
drawCachedGlyph(cachedGlyph, penX, penY);
}
penX += (cachedGlyph->mAdvance.x >> 6);
// If we were given a specific number of glyphs, decrement
if(numGlyphs > 0) {
glyphsLeft --;
}
}
}
void Font::updateGlyphCache(CachedGlyphInfo *glyph)
{
if(!glyph->mBitmapValid) {
FT_Error error = FT_Load_Glyph( mFace, glyph->mGlyphIndex, FT_LOAD_RENDER );
if(error) {
LOGE("Couldn't load glyph.");
return;
}
glyph->mAdvance = mFace->glyph->advance;
glyph->mBitmapLeft = mFace->glyph->bitmap_left;
glyph->mBitmapTop = mFace->glyph->bitmap_top;
FT_Bitmap *bitmap = &mFace->glyph->bitmap;
FT_Bitmap_New(&glyph->mBitmap);
FT_Bitmap_Copy(mRSC->mStateFont.mLibrary, bitmap, &glyph->mBitmap);
glyph->mBitmapValid = true;
}
// Now copy the bitmap into the cache texture
uint32_t startX = 0;
uint32_t startY = 0;
// Let the font state figure out where to put the bitmap
FontState *state = &mRSC->mStateFont;
glyph->mIsValid = state->cacheBitmap(&glyph->mBitmap, &startX, &startY);
if(!glyph->mIsValid) {
return;
}
uint32_t endX = startX + glyph->mBitmap.width;
uint32_t endY = startY + glyph->mBitmap.rows;
glyph->mBitmapMinX = startX;
glyph->mBitmapMinY = startY;
glyph->mBitmapWidth = glyph->mBitmap.width;
glyph->mBitmapHeight = glyph->mBitmap.rows;
uint32_t cacheWidth = state->getCacheTextureType()->getDimX();
uint32_t cacheHeight = state->getCacheTextureType()->getDimY();
glyph->mBitmapMinU = (float)startX / (float)cacheWidth;
glyph->mBitmapMinV = (float)startY / (float)cacheHeight;
glyph->mBitmapMaxU = (float)endX / (float)cacheWidth;
glyph->mBitmapMaxV = (float)endY / (float)cacheHeight;
}
Font::CachedGlyphInfo *Font::cacheGlyph(uint32_t glyph)
{
CachedGlyphInfo *newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
newGlyph->mGlyphIndex = FT_Get_Char_Index(mFace, glyph);
newGlyph->mIsValid = false;
newGlyph->mBitmapValid = false;
//LOGE("Glyph = %c, face index: %u", (unsigned char)glyph, newGlyph->mGlyphIndex);
updateGlyphCache(newGlyph);
return newGlyph;
}
Font * Font::create(Context *rsc, const char *name, uint32_t fontSize, uint32_t dpi)
{
Vector<Font*> &activeFonts = rsc->mStateFont.mActiveFonts;
for(uint32_t i = 0; i < activeFonts.size(); i ++) {
Font *ithFont = activeFonts[i];
if(ithFont->mFontName == name && ithFont->mFontSize == fontSize && ithFont->mDpi == dpi) {
ithFont->incUserRef();
return ithFont;
}
}
Font *newFont = new Font(rsc);
bool isInitialized = newFont->init(name, fontSize, dpi);
if(isInitialized) {
newFont->incUserRef();
activeFonts.push(newFont);
return newFont;
}
delete newFont;
return NULL;
}
Font::~Font()
{
if(mFace) {
FT_Done_Face(mFace);
}
for (uint32_t ct = 0; ct < mRSC->mStateFont.mActiveFonts.size(); ct++) {
if (mRSC->mStateFont.mActiveFonts[ct] == this) {
mRSC->mStateFont.mActiveFonts.removeAt(ct);
break;
}
}
for(uint32_t i = 0; i < mCachedGlyphs.size(); i ++) {
CachedGlyphInfo *glyph = mCachedGlyphs.valueAt(i);
if(glyph->mBitmapValid) {
FT_Bitmap_Done(mRSC->mStateFont.mLibrary, &glyph->mBitmap);
}
delete glyph;
}
}
FontState::FontState()
{
mInitialized = false;
mMaxNumberOfQuads = 1024;
mCurrentQuadIndex = 0;
mRSC = NULL;
}
FontState::~FontState()
{
for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
delete mCacheLines[i];
}
rsAssert(!mActiveFonts.size());
}
void FontState::init(Context *rsc)
{
FT_Error error;
if(!mLibrary) {
error = FT_Init_FreeType(&mLibrary);
if(error) {
LOGE("Unable to initialize freetype");
return;
}
}
mRSC = rsc;
mDefault.set(Font::create(rsc, "DroidSans.ttf", 16, 96));
}
void FontState::flushAllAndInvalidate()
{
if(mCurrentQuadIndex != 0) {
issueDrawCommand();
mCurrentQuadIndex = 0;
}
for(uint32_t i = 0; i < mActiveFonts.size(); i ++) {
mActiveFonts[i]->invalidateTextureCache();
}
for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
mCacheLines[i]->mCurrentCol = 0;
}
}
bool FontState::cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY)
{
// If the glyph is too tall, don't cache it
if((uint32_t)bitmap->rows > mCacheLines[mCacheLines.size()-1]->mMaxHeight) {
LOGE("Font size to large to fit in cache. width, height = %i, %i", (int)bitmap->width, (int)bitmap->rows);
return false;
}
// Now copy the bitmap into the cache texture
uint32_t startX = 0;
uint32_t startY = 0;
bool bitmapFit = false;
for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
bitmapFit = mCacheLines[i]->fitBitmap(bitmap, &startX, &startY);
if(bitmapFit) {
break;
}
}
// If the new glyph didn't fit, flush the state so far and invalidate everything
if(!bitmapFit) {
flushAllAndInvalidate();
// Try to fit it again
for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
bitmapFit = mCacheLines[i]->fitBitmap(bitmap, &startX, &startY);
if(bitmapFit) {
break;
}
}
// if we still don't fit, something is wrong and we shouldn't draw
if(!bitmapFit) {
LOGE("Bitmap doesn't fit in cache. width, height = %i, %i", (int)bitmap->width, (int)bitmap->rows);
return false;
}
}
*retOriginX = startX;
*retOriginY = startY;
uint32_t endX = startX + bitmap->width;
uint32_t endY = startY + bitmap->rows;
//LOGE("Bitmap width, height = %i, %i", (int)bitmap->width, (int)bitmap->rows);
uint32_t cacheWidth = getCacheTextureType()->getDimX();
unsigned char *cacheBuffer = (unsigned char*)mTextTexture->getPtr();
unsigned char *bitmapBuffer = bitmap->buffer;
uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
for(cacheX = startX, bX = 0; cacheX < endX; cacheX ++, bX ++) {
for(cacheY = startY, bY = 0; cacheY < endY; cacheY ++, bY ++) {
unsigned char tempCol = bitmapBuffer[bY * bitmap->width + bX];
cacheBuffer[cacheY*cacheWidth + cacheX] = tempCol;
}
}
// This will dirty the texture and the shader so next time
// we draw it will upload the data
mTextTexture->deferedUploadToTexture(mRSC, false, 0);
mFontShaderF->bindTexture(0, mTextTexture.get());
// Some debug code
/*for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
LOGE("Cache Line: H: %u Empty Space: %f",
mCacheLines[i]->mMaxHeight,
(1.0f - (float)mCacheLines[i]->mCurrentCol/(float)mCacheLines[i]->mMaxWidth)*100.0f);
}*/
return true;
}
void FontState::initRenderState()
{
uint32_t tmp[5] = {
RS_TEX_ENV_MODE_REPLACE, 1,
RS_TEX_ENV_MODE_NONE, 0,
0
};
ProgramFragment *pf = new ProgramFragment(mRSC, tmp, 5);
mFontShaderF.set(pf);
mFontShaderF->init(mRSC);
Sampler *sampler = new Sampler(mRSC, RS_SAMPLER_NEAREST, RS_SAMPLER_NEAREST,
RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP);
mFontSampler.set(sampler);
mFontShaderF->bindSampler(0, sampler);
ProgramStore *fontStore = new ProgramStore(mRSC);
mFontProgramStore.set(fontStore);
mFontProgramStore->setDepthFunc(RS_DEPTH_FUNC_ALWAYS);
mFontProgramStore->setBlendFunc(RS_BLEND_SRC_SRC_ALPHA, RS_BLEND_DST_ONE_MINUS_SRC_ALPHA);
mFontProgramStore->setDitherEnable(false);
mFontProgramStore->setDepthMask(false);
}
void FontState::initTextTexture()
{
const Element *alphaElem = Element::create(mRSC, RS_TYPE_UNSIGNED_8, RS_KIND_PIXEL_A, true, 1);
// We will allocate a texture to initially hold 32 character bitmaps
Type *texType = new Type(mRSC);
texType->setElement(alphaElem);
texType->setDimX(1024);
texType->setDimY(256);
texType->compute();
Allocation *cacheAlloc = new Allocation(mRSC, texType);
mTextTexture.set(cacheAlloc);
mTextTexture->deferedUploadToTexture(mRSC, false, 0);
// Split up our cache texture into lines of certain widths
int nextLine = 0;
mCacheLines.push(new CacheTextureLine(16, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(24, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(32, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(32, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(40, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(texType->getDimY() - nextLine, texType->getDimX(), nextLine, 0));
}
// Avoid having to reallocate memory and render quad by quad
void FontState::initVertexArrayBuffers()
{
// Now lets write index data
const Element *indexElem = Element::create(mRSC, RS_TYPE_UNSIGNED_16, RS_KIND_USER, false, 1);
Type *indexType = new Type(mRSC);
uint32_t numIndicies = mMaxNumberOfQuads * 6;
indexType->setDimX(numIndicies);
indexType->setElement(indexElem);
indexType->compute();
Allocation *indexAlloc = new Allocation(mRSC, indexType);
uint16_t *indexPtr = (uint16_t*)indexAlloc->getPtr();
// Four verts, two triangles , six indices per quad
for(uint32_t i = 0; i < mMaxNumberOfQuads; i ++) {
int i6 = i * 6;
int i4 = i * 4;
indexPtr[i6 + 0] = i4 + 0;
indexPtr[i6 + 1] = i4 + 1;
indexPtr[i6 + 2] = i4 + 2;
indexPtr[i6 + 3] = i4 + 0;
indexPtr[i6 + 4] = i4 + 2;
indexPtr[i6 + 5] = i4 + 3;
}
indexAlloc->deferedUploadToBufferObject(mRSC);
mIndexBuffer.set(indexAlloc);
const Element *posElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 3);
const Element *texElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 2);
const Element *elemArray[2];
elemArray[0] = posElem;
elemArray[1] = texElem;
String8 posName("position");
String8 texName("texture0");
const char *nameArray[2];
nameArray[0] = posName.string();
nameArray[1] = texName.string();
size_t lengths[2];
lengths[0] = posName.size();
lengths[1] = texName.size();
const Element *vertexDataElem = Element::create(mRSC, 2, elemArray, nameArray, lengths);
Type *vertexDataType = new Type(mRSC);
vertexDataType->setDimX(mMaxNumberOfQuads * 4);
vertexDataType->setElement(vertexDataElem);
vertexDataType->compute();
Allocation *vertexAlloc = new Allocation(mRSC, vertexDataType);
mTextMeshPtr = (float*)vertexAlloc->getPtr();
mVertexArray.set(vertexAlloc);
}
// We don't want to allocate anything unless we actually draw text
void FontState::checkInit()
{
if(mInitialized) {
return;
}
initTextTexture();
initRenderState();
initVertexArrayBuffers();
/*mTextMeshRefs = new ObjectBaseRef<SimpleMesh>[mNumMeshes];
for(uint32_t i = 0; i < mNumMeshes; i ++){
SimpleMesh *textMesh = createTextMesh();
mTextMeshRefs[i].set(textMesh);
}*/
mInitialized = true;
}
void FontState::issueDrawCommand() {
ObjectBaseRef<const ProgramVertex> tmpV(mRSC->getVertex());
mRSC->setVertex(mRSC->getDefaultProgramVertex());
ObjectBaseRef<const ProgramFragment> tmpF(mRSC->getFragment());
mRSC->setFragment(mFontShaderF.get());
ObjectBaseRef<const ProgramStore> tmpPS(mRSC->getFragmentStore());
mRSC->setFragmentStore(mFontProgramStore.get());
if (!mRSC->setupCheck()) {
mRSC->setVertex((ProgramVertex *)tmpV.get());
mRSC->setFragment((ProgramFragment *)tmpF.get());
mRSC->setFragmentStore((ProgramStore *)tmpPS.get());
return;
}
float *vtx = (float*)mVertexArray->getPtr();
float *tex = vtx + 3;
VertexArray va;
va.add(GL_FLOAT, 3, 20, false, (uint32_t)vtx, "position");
va.add(GL_FLOAT, 2, 20, false, (uint32_t)tex, "texture0");
va.setupGL2(mRSC, &mRSC->mStateVertexArray, &mRSC->mShaderCache);
mIndexBuffer->uploadCheck(mRSC);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->getBufferObjectID());
glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, (uint16_t *)(0));
// Reset the state
mRSC->setVertex((ProgramVertex *)tmpV.get());
mRSC->setFragment((ProgramFragment *)tmpF.get());
mRSC->setFragmentStore((ProgramStore *)tmpPS.get());
}
void FontState::appendMeshQuad(float x1, float y1, float z1,
float u1, float v1,
float x2, float y2, float z2,
float u2, float v2,
float x3, float y3, float z3,
float u3, float v3,
float x4, float y4, float z4,
float u4, float v4)
{
const uint32_t vertsPerQuad = 4;
const uint32_t floatsPerVert = 5;
float *currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
// Cull things that are off the screen
float width = (float)mRSC->getWidth();
float height = (float)mRSC->getHeight();
if(x1 > width || y1 < 0.0f || x2 < 0 || y4 > height) {
return;
}
/*LOGE("V0 x: %f y: %f z: %f", x1, y1, z1);
LOGE("V1 x: %f y: %f z: %f", x2, y2, z2);
LOGE("V2 x: %f y: %f z: %f", x3, y3, z3);
LOGE("V3 x: %f y: %f z: %f", x4, y4, z4);*/
(*currentPos++) = x1;
(*currentPos++) = y1;
(*currentPos++) = z1;
(*currentPos++) = u1;
(*currentPos++) = v1;
(*currentPos++) = x2;
(*currentPos++) = y2;
(*currentPos++) = z2;
(*currentPos++) = u2;
(*currentPos++) = v2;
(*currentPos++) = x3;
(*currentPos++) = y3;
(*currentPos++) = z3;
(*currentPos++) = u3;
(*currentPos++) = v3;
(*currentPos++) = x4;
(*currentPos++) = y4;
(*currentPos++) = z4;
(*currentPos++) = u4;
(*currentPos++) = v4;
mCurrentQuadIndex ++;
if(mCurrentQuadIndex == mMaxNumberOfQuads) {
issueDrawCommand();
mCurrentQuadIndex = 0;
}
}
void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y)
{
checkInit();
String8 text8(text);
// Render code here
Font *currentFont = mRSC->getFont();
currentFont->renderUTF(text, len, startIndex, numGlyphs, x, y);
if(mCurrentQuadIndex != 0) {
issueDrawCommand();
mCurrentQuadIndex = 0;
}
}
void FontState::renderText(const char *text, int x, int y)
{
size_t textLen = strlen(text);
renderText(text, textLen, 0, -1, x, y);
}
void FontState::renderText(Allocation *alloc, int x, int y)
{
if(!alloc) {
return;
}
const char *text = (const char *)alloc->getPtr();
size_t allocSize = alloc->getType()->getSizeBytes();
renderText(text, allocSize, 0, -1, x, y);
}
void FontState::renderText(Allocation *alloc, uint32_t start, int len, int x, int y)
{
if(!alloc) {
return;
}
const char *text = (const char *)alloc->getPtr();
size_t allocSize = alloc->getType()->getSizeBytes();
renderText(text, allocSize, start, len, x, y);
}
void FontState::deinit(Context *rsc)
{
if(mLibrary) {
FT_Done_FreeType( mLibrary );
}
delete mDefault.get();
mDefault.clear();
}
namespace android {
namespace renderscript {
RsFont rsi_FontCreateFromFile(Context *rsc, char const *name, uint32_t fontSize, uint32_t dpi)
{
return Font::create(rsc, name, fontSize, dpi);
}
} // renderscript
} // android

209
libs/rs/rsFont.h Normal file
View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_RS_FONT_H
#define ANDROID_RS_FONT_H
#include "RenderScript.h"
#include "rsStream.h"
#include <utils/String8.h>
#include <utils/Vector.h>
#include <utils/KeyedVector.h>
#include <ft2build.h>
#include FT_FREETYPE_H
// ---------------------------------------------------------------------------
namespace android {
namespace renderscript {
class FontState;
class Font : public ObjectBase
{
public:
~Font();
// Pointer to the utf data, length of data, where to start, number of glyphs ot read
// (each glyph may be longer than a char because we are dealing with utf data)
// Last two variables are the initial pen position
void renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y);
// Currently files do not get serialized,
// but we need to inherit from ObjectBase for ref tracking
virtual void serialize(OStream *stream) const {
}
virtual RsA3DClassID getClassId() const {
return RS_A3D_CLASS_ID_UNKNOWN;
}
static Font * create(Context *rsc, const char *name, uint32_t fontSize, uint32_t dpi);
protected:
friend class FontState;
void invalidateTextureCache();
struct CachedGlyphInfo
{
// Has the cache been invalidated?
bool mIsValid;
// Location of the cached glyph in the bitmap
// in case we need to resize the texture
uint32_t mBitmapMinX;
uint32_t mBitmapMinY;
uint32_t mBitmapWidth;
uint32_t mBitmapHeight;
// Also cache texture coords for the quad
float mBitmapMinU;
float mBitmapMinV;
float mBitmapMaxU;
float mBitmapMaxV;
// Minimize how much we call freetype
FT_UInt mGlyphIndex;
FT_Vector mAdvance;
// Values below contain a glyph's origin in the bitmap
FT_Int mBitmapLeft;
FT_Int mBitmapTop;
// Hold on to the bitmap in case cache is invalidated
FT_Bitmap mBitmap;
bool mBitmapValid;
};
String8 mFontName;
uint32_t mFontSize;
uint32_t mDpi;
Font(Context *rsc);
bool init(const char *name, uint32_t fontSize, uint32_t dpi);
FT_Face mFace;
bool mInitialized;
bool mHasKerning;
DefaultKeyedVector<uint32_t, CachedGlyphInfo* > mCachedGlyphs;
CachedGlyphInfo *cacheGlyph(uint32_t glyph);
void updateGlyphCache(CachedGlyphInfo *glyph);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
};
class FontState
{
public:
FontState();
~FontState();
void init(Context *rsc);
void deinit(Context *rsc);
ObjectBaseRef<Font> mDefault;
ObjectBaseRef<Font> mLast;
void renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y);
void renderText(const char *text, int x, int y);
void renderText(Allocation *alloc, int x, int y);
void renderText(Allocation *alloc, uint32_t start, int len, int x, int y);
protected:
friend class Font;
struct CacheTextureLine
{
uint32_t mMaxHeight;
uint32_t mMaxWidth;
uint32_t mCurrentRow;
uint32_t mCurrentCol;
CacheTextureLine(uint32_t maxHeight, uint32_t maxWidth, uint32_t currentRow, uint32_t currentCol) :
mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mCurrentCol(currentCol) {
}
bool fitBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY) {
if((uint32_t)bitmap->rows > mMaxHeight) {
return false;
}
if(mCurrentCol + (uint32_t)bitmap->width < mMaxWidth) {
*retOriginX = mCurrentCol;
*retOriginY = mCurrentRow;
mCurrentCol += bitmap->width;
return true;
}
return false;
}
};
Vector<CacheTextureLine*> mCacheLines;
Context *mRSC;
// Free type library, we only need one copy
FT_Library mLibrary;
Vector<Font*> mActiveFonts;
// Render state for the font
ObjectBaseRef<ProgramFragment> mFontShaderF;
ObjectBaseRef<Sampler> mFontSampler;
ObjectBaseRef<ProgramStore> mFontProgramStore;
void initRenderState();
// Texture to cache glyph bitmaps
ObjectBaseRef<Allocation> mTextTexture;
void initTextTexture();
bool cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY);
const Type* getCacheTextureType() {
return mTextTexture->getType();
}
void flushAllAndInvalidate();
// Pointer to vertex data to speed up frame to frame work
float *mTextMeshPtr;
uint32_t mCurrentQuadIndex;
uint32_t mMaxNumberOfQuads;
void initVertexArrayBuffers();
ObjectBaseRef<Allocation> mIndexBuffer;
ObjectBaseRef<Allocation> mVertexArray;
bool mInitialized;
void checkInit();
void issueDrawCommand();
void appendMeshQuad(float x1, float y1, float z1,
float u1, float v1,
float x2, float y2, float z2,
float u2, float v2,
float x3, float y3, float z3,
float u3, float v3,
float x4, float y4, float z4,
float u4, float v4);
};
}
}
#endif

View File

@@ -309,6 +309,24 @@ static uint32_t SC_getHeight()
return rsc->getHeight();
}
static void SC_DrawTextAlloc(RsAllocation va, int x, int y)
{
GET_TLS();
Allocation *alloc = static_cast<Allocation *>(va);
rsc->mStateFont.renderText(alloc, x, y);
}
static void SC_DrawText(const char *text, int x, int y)
{
GET_TLS();
rsc->mStateFont.renderText(text, x, y);
}
static void SC_BindFont(RsFont font)
{
GET_TLS();
rsi_ContextBindFont(rsc, font);
}
//////////////////////////////////////////////////////////////////////////////
// Class implementation
@@ -360,6 +378,11 @@ static ScriptCState::SymbolTable_t gSyms[] = {
{ "rsgClearColor", (void *)&SC_ClearColor },
{ "rsgClearDepth", (void *)&SC_ClearDepth },
{ "_Z11rsgDrawTextPKcii", (void *)&SC_DrawText },
{ "_Z11rsgDrawText13rs_allocationii", (void *)&SC_DrawTextAlloc },
{ "rsgBindFont", (void *)&SC_BindFont },
//////////////////////////////////////
// IO

View File

@@ -89,7 +89,9 @@ void Type::compute()
mLODCount = 1;
}
if (mLODCount != oldLODCount) {
delete [] mLODs;
if(mLODs){
delete [] mLODs;
}
mLODs = new LOD[mLODCount];
}

View File

@@ -35,6 +35,10 @@ extern void __attribute__((overloadable)) rsgDrawSimpleMesh(rs_mesh ism, int sta
extern void rsgClearColor(float, float, float, float);
extern void rsgClearDepth(float);
extern void __attribute__((overloadable)) rsgDrawText(const char *, int x, int y);
extern void __attribute__((overloadable)) rsgDrawText(rs_allocation, int x, int y);
extern void rsgBindFont(rs_font);
///////////////////////////////////////////////////////
// misc
extern void color(float, float, float, float);

View File

@@ -24,6 +24,7 @@ typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_fragme
typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_vertex;
typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_raster;
typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_store;
typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_font;
typedef float float2 __attribute__((ext_vector_type(2)));
typedef float float3 __attribute__((ext_vector_type(3)));