chore: checkpoint web Blender migration source, tools and docs
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <functional>
#include "gpu_backend.hh"
namespace blender::gpu {
/* The caller keeps the framebuffer and its attachments alive until restoration. */
std::function<void()> webgl_framebuffer_state_capture(FrameBuffer *framebuffer);
/* Replay a native viewport-index draw against the framebuffer's original array. */
void webgl_framebuffer_viewport_draw(const std::function<void(int)> &submit);
/* Native bindings only: restoring a scope never rolls back buffer/texture contents. */
std::function<void()> webgl_resource_state_capture();
/* CPU selection caches still belong to the browser context that rendered them. */
std::function<bool()> webgl_select_cache_validity_capture();
/* Retire a cached selection owner when a native gesture is cancelled. */
void webgl_select_cache_invalidate();
void webgl_select_check_interrupt();
/* Keep ordinary UBO bindings below this slot. WebGL2 has no SSBO binding namespace. */
constexpr int WEBGL_GLYPH_UBO_SLOT = 15;
/* Workbench's read-only material SSBO uses four R32UI texels per native material.
* Keep this separate from its image/matcap samplers and geometry fetch bindings. */
constexpr int WEBGL_WORKBENCH_MATERIAL_TEXTURE_SLOT = 15;
constexpr int WEBGL_OBJECT_MATRIX_TEXTURE_SLOT = 14;
constexpr int WEBGL_OBJECT_INFO_TEXTURE_SLOT = 13;
constexpr int WEBGL_RESOURCE_ID_TEXTURE_SLOT = 12;
constexpr int WEBGL_OBJECT_ATTRIBUTE_TEXTURE_SLOT = 11;
constexpr int WEBGL_OVERLAY_INSTANCE_TEXTURE_SLOT = 10;
constexpr int WEBGL_SELECT_INPUT_TEXTURE_SLOT = 9;
/* GP geometry uses samplers 0..5 and Manager slots 11..14. Its original
* 255 * 112 byte material UBO exceeds WebGL2's guaranteed 16 KiB block. */
constexpr int WEBGL_GP_MATERIAL_TEXTURE_SLOT = 8;
constexpr int WEBGL_GP_MATERIAL_UBO_SLOT = 4;
constexpr int WEBGL_SHADOW_VIEW_TEXTURE_SLOT = 7;
constexpr int WEBGL_SHADOW_VIEW_STORAGE_SLOT = 5;
constexpr size_t WEBGL_SHADOW_VIEW_BYTES = 64 * 48;
/* eevee_defines.hh: one uint page address for each of 32*32 tiles in 64 views.
* Keep the fragment map separate from the vertex render-view texture. */
constexpr int WEBGL_SHADOW_MAP_TEXTURE_SLOT = 6;
constexpr int WEBGL_SHADOW_MAP_STORAGE_SLOT = 3;
constexpr size_t WEBGL_SHADOW_MAP_BYTES = 64 * 32 * 32 * sizeof(uint32_t);
constexpr size_t WEBGL_GP_MATERIAL_BYTES = 255 * 112;
/* Original select_defines.hh bindings (separate UBO/SSBO namespaces). */
constexpr int WEBGL_SELECT_INFO_SLOT = 4;
constexpr int WEBGL_SELECT_INPUT_SLOT = 5;
constexpr int WEBGL_SELECT_OUTPUT_SLOT = 6;
/* Native draw_defines.hh storage bindings; these are not WebGL texture units. */
constexpr int WEBGL_OBJECT_MATRIX_STORAGE_SLOT = 10;
constexpr int WEBGL_OBJECT_INFO_STORAGE_SLOT = 9;
constexpr int WEBGL_RESOURCE_ID_STORAGE_SLOT = 11;
constexpr int WEBGL_OBJECT_ATTRIBUTE_STORAGE_SLOT = 8;
/** WebGL2 context and window framebuffer backend. Shader/resource support is still incomplete. */
class WebGLBackend : public GPUBackend {
public:
WebGLBackend();
~WebGLBackend() override;
static bool is_supported();
void init_resources() override;
void delete_resources() override;
Context *context_alloc(GHOST_IWindow *window, GHOST_IContext *context) override;
FrameBuffer *framebuffer_alloc(const char *name) override;
TexturePool *texturepool_alloc() override;
Fence *fence_alloc() override;
Batch *batch_alloc() override;
IndexBuf *indexbuf_alloc() override;
PixelBuffer *pixelbuf_alloc(size_t size) override;
QueryPool *querypool_alloc() override;
Shader *shader_alloc(const char *name) override;
Texture *texture_alloc(const char *name) override;
UniformBuf *uniformbuf_alloc(size_t size, const char *name) override;
StorageBuf *storagebuf_alloc(size_t size, GPUUsageType usage, const char *name) override;
VertBuf *vertbuf_alloc() override;
void compute_dispatch(int x, int y, int z) override;
void compute_dispatch_indirect(StorageBuf *buffer) override;
void shader_cache_dir_clear_old() override;
void render_begin() override;
void render_end() override;
void render_step(bool force_resource_release) override;
};
} // namespace blender::gpu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstddef>
#include <cstdint>
#include <vector>
namespace blender::gpu {
class Batch;
class IndexBuf;
class VertBuf;
class Immediate;
class StorageBuf;
Batch *webgl_batch_alloc();
IndexBuf *webgl_indexbuf_alloc();
VertBuf *webgl_vertbuf_alloc();
Immediate *webgl_immediate_alloc();
/* Read only synchronized native command storage, with bounds checked before allocation. */
std::vector<uint32_t> webgl_indirect_snapshot(StorageBuf *buffer, size_t offset, size_t size);
} // namespace blender::gpu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "GPU_framebuffer.hh"
#include "GPU_state.hh"
#include "GPU_uniform_buffer.hh"
namespace blender::gpu {
class Shader;
class StorageBuf;
class UniformBuf;
class VertBuf;
class IndexBuf;
class Context;
class Texture;
/* A native caller can span several dispatches without publishing a partial
* capture on failure. Recording is explicit: unrelated capture raster/compute
* work is not part of the persistent atlas/lighting transaction. */
class WebGLComputeTransaction {
struct State;
std::unique_ptr<State> state_;
public:
WebGLComputeTransaction();
~WebGLComputeTransaction();
void commit() noexcept;
/* Enlist native caller metadata/owner replacement in the active transaction.
* Register before mutation. The callback must not throw or allocate, and its
* caller-owned references must outlive the synchronous transaction. Captured
* resources remain alive until rollback finishes or the transaction commits. */
static void record_rollback(std::function<void()> restore);
WebGLComputeTransaction(const WebGLComputeTransaction &) = delete;
WebGLComputeTransaction &operator=(const WebGLComputeTransaction &) = delete;
class Recording {
public:
Recording();
~Recording();
Recording(const Recording &) = delete;
Recording &operator=(const Recording &) = delete;
};
};
/* Prepared on the engine thread after readback. Applying a prepared write must
* not allocate, call browser APIs or fail: every owner is committed as one batch. */
struct WebGLComputeCommit {
void *owner;
const void *data;
size_t size;
void (*apply)(void *owner, const void *data, size_t size) noexcept;
void commit() const noexcept { apply(owner, data, size); }
};
WebGLComputeCommit webgl_storagebuf_compute_prepare(StorageBuf *buffer,
const void *data,
size_t size);
WebGLComputeCommit webgl_vertbuf_compute_prepare(VertBuf *buffer, const void *data, size_t size);
void webgl_vertbuf_compute_barrier(VertBuf *buffer);
WebGLComputeCommit webgl_indexbuf_compute_prepare(IndexBuf *buffer, const void *data, size_t size);
void webgl_indexbuf_compute_barrier(IndexBuf *buffer);
WebGLComputeCommit webgl_uniformbuf_compute_prepare(UniformBuf *buffer,
const void *data,
size_t size);
void webgl_uniformbuf_compute_barrier(UniformBuf *buffer);
/* Materialize a known physical raster UBO before its native caller publishes
* display state. Unlike a generic barrier, this also creates the first cache. */
void webgl_uniformbuf_publish_raster(UniformBuf *buffer);
/* Copy committed owner bytes, including deferred CPU initialization, without a GL readback. */
void webgl_uniformbuf_read(UniformBuf *buffer, void *data, size_t size);
/* Native buffers own broker allocation lifetimes. Each dispatch synchronizes host snapshots. */
bool webgl_compute_active();
std::function<void()> webgl_shader_state_capture();
/* Physical browser bindings differ from native CreateInfo slots (GP material
* UBOs and draw SSBOs use textures, while glyph SSBOs use a UBO). */
struct WebGLRasterResourceUsage {
uint64_t textures = 0;
uint64_t owner_textures = 0;
uint32_t uniform_buffers = 0;
};
WebGLRasterResourceUsage webgl_raster_resource_usage();
void webgl_raster_clear_storage_slot(int slot);
/* The bound raster CreateInfo, not an allocation name/size, selects the read-only view. */
struct WebGLRasterStorageBinding {
int texture_slot = -1;
size_t record_size = 0;
bool selection_output = false;
bool shadow_tag_output = false;
};
WebGLRasterStorageBinding webgl_shader_storage_binding(int slot);
struct WebGLRasterUniformBinding {
int texture_slot = -1;
size_t byte_size = 0;
};
WebGLRasterUniformBinding webgl_shader_uniform_binding(int slot);
void webgl_compute_bind_image(int slot, Texture *texture);
void webgl_compute_unbind_image(Texture *texture);
void webgl_compute_bind_uniform(int slot, const void *owner, const void *data, size_t size);
void webgl_compute_bind_storage(int slot, StorageBuf *buffer, size_t size);
void webgl_compute_bind_uniform_as_storage(int slot, UniformBuf *buffer, size_t size);
void webgl_compute_bind_vertbuf(int slot, VertBuf *buffer, size_t size);
void webgl_compute_bind_indexbuf(int slot, IndexBuf *buffer, size_t size);
void webgl_compute_unbind_buffer(const void *buffer);
void webgl_compute_forget_buffer(const void *buffer);
void webgl_compute_forget_context(Context *context);
void webgl_compute_dispatch(int x, int y, int z);
void webgl_compute_dispatch_indirect(StorageBuf *buffer);
void webgl_compute_barrier(GPUBarrier barriers);
/* Publish the raster cache of a committed native storage owner without rebinding it. */
void webgl_storagebuf_compute_barrier(StorageBuf *buffer, GPUBarrier barriers);
/* Materialize a native read-only word table without changing shader/slot bindings.
* CPU updates are not compute writes and need not appear in a barrier journal. */
void webgl_storagebuf_publish_raster(StorageBuf *buffer);
/* Scope the native ShadowModule mode; return the previous value for nested draws. */
bool webgl_shadow_atlas_debug_cost_set(bool debug_cost);
bool webgl_shader_shadow_cost(Shader *shader);
Shader *webgl_shader_alloc(const char *name);
void webgl_shader_set_base_instance(Shader *shader, int first);
/* Replay the original selection draw into GPU depth/ID attachments on WebGL2. */
/* Submit through native selection and restore draw-local uniforms even on failure. */
void webgl_shader_draw(Shader *shader, const std::function<void()> &draw, int instance_first = 0,
uint32_t shadow_fragment_bound = 0);
/* Native GP depth, outline and material consumers share bit-encoded vertex data. */
int webgl_shader_encoded_vertex_id_slot(Shader *shader);
/* Original GP resolve and ShadowModule debug fullscreen consumers have two-pass lowering. */
bool webgl_shader_supports_custom_blend(Shader *shader);
void webgl_shader_apply_clip_distances(Shader *shader, int count);
void webgl_shader_apply_texture_swizzles(Shader *shader, Span<const int4> swizzles);
void webgl_shader_apply_subpass_inputs(Shader *shader, Span<const GPUAttachment> attachments);
} // namespace blender::gpu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <GLES3/gl3.h>
#include <emscripten/html5.h>
#include <memory>
#include <list>
#include <string>
#include <vector>
#include "gpu_texture_private.hh"
#include "webgl_shader.hh"
#include "webgl_texture_state.hh"
namespace blender::gpu {
class WebGLTexture;
/* Resolve the existing native StateManager binding, including its lifetime and sampler. */
WebGLTexture *webgl_compute_sample_binding(int slot, GPUSamplerState &state);
struct WebGLComputeSample {
std::vector<uint32_t> pixels;
std::string descriptor;
};
/* Shared lifetime/revision, without retaining the VBO that owns its cached texture. */
struct WebGLBufferTextureSource {
VertBuf *buffer = nullptr;
uint64_t revision = 0;
};
std::shared_ptr<WebGLBufferTextureSource> webgl_buffer_texture_source(VertBuf *buffer);
/* Texture transfers use isolated attachment views, never a native render-pass bind. */
void webgl_texture_clear_attachment(FrameBuffer *fb, GPUAttachmentType type, double4 value);
void webgl_texture_copy_attachment(FrameBuffer *src,
FrameBuffer *dst,
GPUFrameBufferBits planes,
int width,
int height);
/* Materialize a compute-written atlas before a native raster consumer binds it. */
void webgl_texture_publish_raster(Texture *texture);
/* Preserve shared layers/mips when a native atlas changes its layer count.
* The destination must be initialized separately; new layers are untouched. */
void webgl_texture_copy_array_overlap(Texture *source, Texture *destination, IndexRange levels);
class WebGLTexture : public Texture {
std::shared_ptr<int> lifetime_ = std::make_shared<int>(0);
GLuint id_ = 0;
/* WebGL has no texture-view objects. Writes alias the retained source; sampling
* uses an isolated GPU copy so simultaneous mip views never alter its LOD range. */
WebGLTexture *view_source_ = nullptr;
int view_mip_ = 0, view_layer_ = 0;
bool syncing_view_ = false;
void sync_view();
void copy_compatible_to(WebGLTexture *destination, IndexRange levels);
/* Context-owned transfer program; depth sampling never uses display/color transforms. */
GLuint depth_read_program_ = 0;
GLuint shadow_seed_program_ = 0;
GLuint shadow_count_program_ = 0;
GLuint shadow_count_page_ = 0;
GLuint shadow_pages_[2] = {};
GLuint shadow_framebuffers_[2] = {};
GLuint shadow_depth_ = 0;
GLuint shadow_vao_ = 0;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE owner_ = 0;
struct Sampler {
GPUSamplerState state;
GLuint id;
};
std::vector<Sampler> samplers_;
/* Bindings retain the mask, not the texture, and observe changes after binding. */
std::shared_ptr<int4> swizzle_ = std::make_shared<int4>(0, 1, 2, 3);
std::shared_ptr<WebGLBufferTextureSource> buffer_source_;
uint64_t buffer_revision_ = 0;
int buffer_width_ = 0, buffer_height_ = 0;
void sync_buffer_texture();
std::vector<uint32_t> compute_pixels_;
bool compute_upload_pending_ = false;
struct ComputeRestore {
int mip, layer, width, height, layers;
eGPUDataFormat format;
std::vector<uint32_t> pixels;
};
std::list<ComputeRestore> compute_restores_;
bool restoring_compute_ = false;
void publish_compute();
public:
explicit WebGLTexture(const char *name) : Texture(name) {}
~WebGLTexture() override;
std::weak_ptr<int> lifetime() const { return lifetime_; }
bool has_live_storage() const;
GLuint id() const;
GLuint attachment_id() const;
int attachment_mip(int mip) const;
int attachment_layer(int layer) const;
int compute_image_layers() const
{
return (type_ == GPU_TEXTURE_2D_ARRAY || type_ == GPU_TEXTURE_3D) ? d_ : 1;
}
eGPUDataFormat compute_image_data_format() const
{
return format_ == TextureFormat::UNORM_8_8_8_8 ? GPU_DATA_UBYTE :
(format_ == TextureFormat::UFLOAT_11_11_10 || format_ == TextureFormat::SFLOAT_32 || format_ == TextureFormat::SFLOAT_32_32_32_32) ? GPU_DATA_FLOAT :
(format_ == TextureFormat::SFLOAT_16 || format_ == TextureFormat::SFLOAT_16_16 || format_ == TextureFormat::SFLOAT_16_16_16_16) ? GPU_DATA_HALF_FLOAT : GPU_DATA_UINT;
}
size_t compute_image_bytes() const;
WebGLComputeSample compute_sample_snapshot(GPUSamplerState state);
WebGLComputeCommit compute_prepare(const void *data, size_t size);
/* Transfer an entire completed atlas without a second atlas-sized allocation. */
WebGLComputeCommit compute_prepare_swap(std::vector<uint32_t> &pixels);
/* Prepare before dispatch; enqueue rollback without allocating or calling GL.
* The parent retains restoration even after the temporary view is destroyed. */
std::function<void()> compute_restore_prepare(const std::vector<uint32_t> &pixels);
GLenum target() const;
/* GPU_texture_create_view(cube_as_array=true) changes the public view target
* while retaining cube-face storage in the source. Framebuffer consumers need
* this bit to select glFramebufferTexture2D rather than a layered attachment. */
bool is_cube_array_view() const
{
return view_source_ && view_source_->type_ == GPU_TEXTURE_CUBE &&
type_ == GPU_TEXTURE_2D_ARRAY;
}
/* During sync_view() the destination is the private sampling copy. Outside
* that transfer, attachments intentionally alias the retained source view. */
bool is_view_transfer() const { return syncing_view_; }
GLuint transfer_attachment_id() const { return syncing_view_ ? id_ : attachment_id(); }
int transfer_attachment_mip(int mip) const
{
return syncing_view_ ? mip : attachment_mip(mip);
}
int transfer_attachment_layer(int layer) const
{
return syncing_view_ ? layer : attachment_layer(layer);
}
std::shared_ptr<const int4> sampling_swizzle() const { return swizzle_; }
GPUSamplerState resolve_sampler(GPUSamplerState state) const
{
return state.type == GPU_SAMPLER_STATE_TYPE_INTERNAL ? sampler_state : state;
}
void bind(GPUSamplerState state, int unit);
void unbind();
void generate_mipmap() override;
void copy_to(Texture *texture, IndexRange mip_levels) override;
void copy_array_overlap_to(WebGLTexture *destination, IndexRange levels);
void clear(double4 value) override;
void swizzle_set(const char mask[4]) override;
void mip_range_set(int min, int max) override;
void read(int mip, eGPUDataFormat format, void *dst) override;
/* Min via radix depth; Add via bounded byte counts accumulated as uints. */
void shadow_atlas_reduce(Span<uint32_t> pages, int texture_unit, int count_unit,
const std::function<void(int, int, int)> &submit);
void read_depth_region(int mip, int layer, const int area[4], float *dst);
/** Read sampled depth as its native IEEE-754 bits for GPU selection caches. */
void read_depth_region_uint(int mip, int layer, const int area[4], uint32_t *dst);
void update_sub(int mip,
int offset[3],
int extent[3],
eGPUDataFormat format,
const void *data,
uint row_length) override;
void update_sub(int offset[3], int extent[3], eGPUDataFormat format,
GPUPixelBuffer *buffer) override;
protected:
bool init_internal() override;
bool init_internal(VertBuf *) override;
bool init_internal(Texture *, int, int, bool) override;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_texture.hh"
#include "GPU_framebuffer.hh"
namespace blender::gpu {
/* Query storage without publishing pending writes. Native region owners must
* recapture GPU-only contents after browser context loss. Keep this query
* usable by WM without importing the GPU backend's private implementation. */
bool webgl_texture_has_live_storage(Texture *texture);
/* Query the native FBO name without binding or reattaching it. Auxiliary
* editor owners use this to retire a same-sized selection target after a
* browser context restore. */
bool webgl_framebuffer_has_live_storage(FrameBuffer *framebuffer);
/* The browser default framebuffer is owned by GHOST, but native WM readback still needs a
* backend-owned liveness check before sampling it after context restoration. */
bool webgl_frontbuffer_is_readable();
/* WM calls this only after GHOST presents a complete native editor frame. */
void webgl_frontbuffer_publish();
} // namespace blender::gpu

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
namespace blender::gpu {
/* Only synchronous editor tool-depth requests consume the WM interrupt signal.
* Keep normal render/compute texture transfers outside this cancellation scope.
* This caller interface deliberately needs no private GPU or GLES headers. */
class WebGLToolDepthScope {
public:
WebGLToolDepthScope();
~WebGLToolDepthScope();
WebGLToolDepthScope(const WebGLToolDepthScope &) = delete;
WebGLToolDepthScope &operator=(const WebGLToolDepthScope &) = delete;
};
void webgl_tool_depth_check_interrupt();
} // namespace blender::gpu