The com.codename1.gpu package provides a portable, hardware accelerated 3D graphics API focused on games but useful anywhere you need GPU rendered content. It runs from one code base on the JavaSE simulator (OpenGL via JOGL), Android (OpenGL ES 2), iOS and macOS (Metal), the native Windows port (Direct3D 11), and the JavaScript port (WebGL). The API integrates with the normal Codename One UI: a 3D scene lives inside a regular component that you add to a Form like any other.

Your Renderer callbacks run on the native platform render thread — NOT the Codename One EDT. This is the single most important thing to understand before you write any 3D code.

onInit, onResize, onFrame and onDispose are invoked on the underlying OS/GPU render loop (GL / Metal / Direct3D), not on the event dispatch thread. From inside them you must not touch Codename One UI components, show forms, or call other EDT-only APIs. To push data back to the UI (for example to update a HUD label or react to a game event) hop onto the EDT with CN.callSerially(…​). Conversely, treat the GraphicsDevice and the objects it hands you as owned by the render thread.

3D is a low-level, GPU dependent feature. Always guard usage with CN.isGpuSupported() (or RenderView.isSupported()); on a platform without a backend the RenderView reports unsupported so you can fall back to 2D.

Concepts

The API is intentionally "hybrid": a low level command layer (buffers, textures, render state, draw calls) for full control, plus high level helpers (meshes, materials, a camera) for common cases. Crucially, you never write shader source. Instead you describe a Material (a lighting model plus color and texture) and the engine generates the matching platform shader behind the scenes: GLSL ES on OpenGL ES and WebGL, GLSL on the desktop simulator, Metal Shading Language on iOS and Mac, and HLSL on the native Windows (Direct3D) port. This "engine-managed shader" approach is what keeps the same application code rendering identically across a range of different GPUs.

The main types are:

  • RenderView - the Component that hosts the GPU surface. You give it a Renderer.

  • Renderer - your callback: onInit, onResize, onFrame, onDispose. These run on the platform render thread, never the EDT.

  • GraphicsDevice - the command surface passed to your renderer. It creates buffers and textures, clears, sets the viewport, the camera and the light, and issues draw calls.

  • Mesh, Material, Camera, Light - the high level scene building blocks.

A first scene: A spinning cube

The renderer below draws a Phong lit cube. Primitives.cube builds the geometry, a Material describes the surface, and a Camera supplies the view. Notice there is no shader code anywhere.

setContinuous(true) runs an animation loop. For a static scene leave it off and call view.requestRender() whenever something changes; this conserves battery.

A Phong lit cube rendered by RenderView
Figure 226. A Phong lit cube rendered by RenderView in the simulator

Materials

A Material is a declarative description of a surface. Its Type selects the lighting model:

TypeDescription

UNLIT

Flat color/texture, no lighting. Ideal for UI, emissive surfaces.

LAMBERT

Diffuse (Lambert) lighting from one directional light.

PHONG

Diffuse + specular highlight (uses setShininess).

PBR

Physically based: metallic and roughness (setMetallic, setRoughness) with a GGX highlight, plus setEmissive for surfaces that glow. GltfLoader fills these from a model’s own materials.

SPRITE

Unlit, for screen aligned sprites and billboards.

SKYBOX

Unlit background, rendered behind the scene.

A material also carries a base color (setColor, packed 0xAARRGGBB), an optional Texture (setTexture), and a RenderState controlling depth testing, alpha blending and face culling. Textures come from a Codename One Image or raw ARGB pixels:

Texture tex = device.createTexture(myImage);     // or createTexture(w, h, argb)
// NEAREST keeps texel edges crisp, which is what the checkerboard in the
// figure below needs. LINEAR is the better default for photographic
// textures, where smoothing is wanted rather than avoided.
tex.setFilter(Texture.Filter.NEAREST).setWrap(Texture.Wrap.REPEAT);
Material m = new Material(Material.Type.UNLIT).setTexture(tex);
A checkerboard textured cube
Figure 227. An UNLIT cube with a checkerboard texture (NEAREST filtering)

Meshes and buffers

Primitives builds common shapes (cube, quad). For custom geometry, allocate a VertexBuffer with a VertexFormat, fill the interleaved float data, and (optionally) an IndexBuffer:

VertexBuffer vb = device.createVertexBuffer(VertexFormat.POSITION_NORMAL_TEXCOORD, 4);
vb.setData(new float[] { /* px,py,pz, nx,ny,nz, u,v  per vertex null */ });
IndexBuffer ib = device.createIndexBuffer(6);
ib.setData(new int[] { 0, 1, 2, 0, 2, 3 });
Mesh mesh = new Mesh(vb, ib, PrimitiveType.TRIANGLES);

Vertex buffers are allocated through the platform SIMD allocator, which on iOS (ParparVM) places the data at a fixed, aligned native address so it can be handed to Metal with no intermediate copy. You don’t need to do anything special to get this; just write into getData() and call setDirty() when you mutate it.

Loading models

Real scenes use authored geometry. GltfLoader loads a glTF 2.0 model, both the binary .glb container and the JSON .gltf form with embedded buffers. loadModel returns the mesh together with the base-color texture from the model’s own material, so a textured model renders with no extra setup:

InputStream in = Display.getInstance().getResourceAsStream(getClass(), "/boombox.glb");
GltfLoader.GltfModel loaded = GltfLoader.loadModel(device, in);
Material material = new Material(Material.Type.PHONG).setShininess(16f);
if (loaded.getBaseColorTexture() != null) {
    material.setTexture(loaded.getBaseColorTexture());
}
Mesh model = loaded.getMesh();           // draw it like any other mesh

The model below is the Khronos "BoomBox" glTF sample (a CC0 model carrying its own base-color texture), loaded this way and drawn Phong lit:

A textured glTF model loaded with GltfLoader
Figure 228. The Khronos BoomBox glTF sample loaded with GltfLoader

Animation

Drive a value (a rotation angle, a position) over time and redraw. A continuous RenderView calls onFrame every frame; an on-demand view redraws when you call requestRender(). The grid below captures six fixed rotation stages of a spinning cube, each drawn into its own viewport in a single frame:

Six rotation stages of an animated cube
Figure 229. Six rotation stages of an animated cube captured in one frame

Camera and math

Camera builds the view and projection matrices from an eye position, a look-at target and lens settings (setPerspective or setOrthographic). All matrix helpers live in Matrix4 (column-major float[16]): translation, scaling, rotation, multiply, lookAt, perspective, ortho. Pass a model matrix as the third argument to draw, or null for the identity.

Atmosphere, shadows and post processing

Three settings turn a correctly lit scene into one that looks shot outdoors through a lens. All three start off, so a scene that never touches them renders exactly as before.

An Environment (GraphicsDevice.setEnvironment, or getEnvironment() on a GameView) holds the atmosphere every draw is shaded in:

  • Fog — exponential squared in distance, thinned with height, so distant geometry melts into the haze.

  • Hemisphere ambient — a sky color lights upward faces and a ground color downward ones, which gives shape to everything in shadow.

  • Tone mapping and exposure — a filmic (ACES) curve, so bright light rolls off instead of clipping.

  • An open sky — setSky paints SKYBOX surfaces as a gradient from the horizon to the zenith, with soft clouds and the sun where the light comes from. Draw it on a large sphere around the camera; it needs no texture.

Light.setShadows(true) renders the scene from the light into a shadow map before each frame, and lit materials are darkened where it’s blocked. Keep setShadowMap’s extent as small as the view allows and move `setShadowCenter with the camera, since the map’s resolution is spread over the area it covers. A material that shouldn’t cast (grass, particles) says so with setCastShadows(false).

A PostProcess on the view renders the scene offscreen and adds bloom, a color grade, a vignette and film grain on the way to the screen. Touch controls are drawn after it and are never graded.

GameView view = new GameView() {
    protected void update(double deltaSeconds) {
    }
};
view.getEnvironment()
        .setFog(0xffb9c6cf, 0.0075f, 0.05f)
        .setHemisphere(0xffa9bdd2, 0xff4f5a3c)
        .setToneMapping(true)
        .setExposure(1.3f)
        .setSky(0xff4f7fb8, 0xffb9c6cf, 0xfffff0d0, 0.45f);
view.getLight().setShadows(true).setShadowMap(2048, 18f);
view.setPostProcess(new PostProcess()
        .setBloom(0.78f, 0.55f)
        .setGrade(1.12f, 0.86f, 0xff0b1720, 0xfffff2dc)
        .setVignette(0.42f)
        .setGrain(0.035f));

Match the horizon color to the fog color and the far edge of the world disappears into the sky. A device that can’t render shadows or offscreen says so through GpuCapabilities.isShadowsSupported() and isPostProcessSupported(), and the scene is then drawn directly. The JavaSE software rasterizer applies the fog, the ambient, tone mapping and the sky gradient, but not shadows, clouds or the post chain.

Platform notes

  • JavaSE simulator - OpenGL through JOGL, loaded from an isolated class so a missing or failing GL driver degrades to a built in software rasterizer instead of breaking the simulator. Lets you develop and debug 3D without a device.

  • Android - OpenGL ES 2 via a GLSurfaceView hosted as a native peer.

  • iOS and macOS - Metal. Shaders are generated as Metal Shading Language and compiled at runtime; vertex/index data is uploaded to `MTLBuffer`s, zero-copy where the SIMD allocation permits.

  • Native Windows - Direct3D 11. Shaders are generated as HLSL and compiled with D3DCompile; the offscreen render target is read back and composited into the UI.

  • JavaScript - WebGL on a <canvas> peer; the generated GLSL ES runs unmodified.

Querying capabilities at runtime:

if (CN.isGpuSupported()) {
    GpuCapabilities caps = device.getCapabilities();
    Log.p("GPU " + caps.getRendererName()
            + ", max texture " + caps.getMaxTextureSize()
            + ", shader level 3: " + caps.isShaderLevel3());

    // Size the atlas to what this device can actually hold rather than
    // assuming a figure: the limit is as low as 2048 on older hardware.
    int edge = Math.min(2048, caps.getMaxTextureSize());
    Log.p("atlas edge " + edge);

    if (caps.isIntIndicesSupported()) {
        // 32-bit indices are available, so a mesh may exceed 65535 vertices
    }
}

Threading

Renderer callbacks run on the platform render thread, not the Codename One EDT. Don’t touch UI components from inside them. To move data the other way (for example to update a HUD label from a game loop), use CN.callSerially(…​).