Coder Social home page Coder Social logo

wgsl_to_wgpu's Introduction

wgsl_to_wgpu

Latest Version docs.rs
An experimental library for generating typesafe Rust bindings from WGSL shaders to wgpu.

wgsl_to_wgpu is designed to be incorporated into the compilation process using a build script. The WGSL shaders are parsed using naga to generate a corresponding Rust module. The generated Rust module contains the type definitions and boilerplate code needed to work with the WGSL shader module. Using the generated code can also reduce many instances of invalid API usage. wgsl_to_wgpu facilitates a shader focused workflow where edits to WGSL code are automatically reflected in the corresponding Rust file. For example, changing the type of a uniform in WGSL will raise a compile error in Rust code using the generated struct to initialize the buffer.

Features

  • more strongly typed bind group and bindings initialization
  • shader module initialization
  • Rust structs for vertex, storage, and uniform buffers
  • optional derives for encase, bytemuck, and serde
  • const validation of WGSL memory layout for generated structs when using bytemuck

Usage

Add the following lines to the Cargo.toml and fill in the appropriate versions for wgsl_to_wgpu. When enabling derives for crates like bytemuck, serde, or encase, these dependencies should also be added to the Cargo.toml with the appropriate derive features. See the provided example project for basic usage.

[build-dependencies]
wgsl_to_wgpu = "..."

See the example crate for how to use the generated code. Run the example with cargo run.

Memory Layout

WGSL structs have different memory layout requirements than Rust structs or standard layout algorithms like repr(C) or repr(packed). Matching the expected layout to share data between the CPU and GPU can be tedious and error prone. wgsl_to_wgpu offers options to add derives for encase to handle padding and alignment at runtime or bytemuck for enforcing padding and alignment at compile time.

When deriving bytemuck, wgsl_to_wgpu will use naga's layout calculations to add const assertions to ensure that all fields of host-shareable types (structs for uniform and storage buffers) have the correct offset, size, and alignment expected by WGSL. It's strongly recommended to use types like vec4 or mat4 instead of vec3 or mat3 with bytemuck to avoid alignment mismatches. Structs used only as vertex input structs have their layout manually specified using std::mem::offset_of and do not generate layout validation assertions.

Bind Groups

wgpu uses resource bindings organized into bind groups to define global shader resources like textures and buffers. Shaders can have many resource bindings organized into up to 4 bind groups. wgsl_to_wgpu will generate types and functions for initializing and setting these bind groups in a more typesafe way. Adding, removing, or changing bind groups in the WGSl shader will typically result in a compile error instead of a runtime error when compiling the code without updating the code for creating or using these bind groups.

While bind groups can easily be set all at once using the bind_groups::set_bind_groups function, it's recommended to organize bindings into bindgroups based on their update frequency. Bind group 0 will change the least frequently like per frame resources with bind group 3 changing most frequently like per draw resources. Bind groups can be set individually using their set(render_pass) method. This can provide a small performance improvement for scenes with many draw calls. See descriptor table frequency (DX12) and descriptor set frequency (Vulkan) for details.

Organizing bind groups in this way can also help to better organize rendering resources in application code instead of redundantly storing all resources with each object. The bindgroups::BindGroup0 may only need to be stored once while bindgroups::BindGroup3 may be stored for each mesh in the scene. Note that bind groups store references to their underlying resource bindings, so it is not necessary to recreate a bind group if the only the uniform or storage buffer contents change. Avoid creating new bind groups during rendering if possible for best performance.

Limitations

  • It may be necessary to disable running this function for shaders with unsupported types or features. Please make an issue if any new or existing WGSL syntax is unsupported.
  • This library is not a rendering library and will not generate any high level abstractions like a material or scene graph. The goal is just to generate most of the tedious and error prone boilerplate required to use WGSL shaders with wgpu.
  • The generated code will not prevent accidentally calling a function from an unrelated generated module. It's recommended to name the shader module with the same name as the shader and use unique shader names to avoid issues. Using generated code from a different shader module may be desirable in some cases such as using the same camera struct definition in multiple WGSL shaders.
  • The current implementation assumes all shader stages are part of a single WGSL source file. Shader modules split across files may be supported in a future release.
  • Uniform and storage buffers can be initialized using the wrong generated Rust struct. WGPU will still validate the size of the buffer binding at runtime.
  • Most but not all WGSL types are currently supported.
  • Vertex attributes using floating point types in WGSL like vec2<f32> are assumed to use float inputs instead of normalized attributes like unorm or snorm integers.
  • All textures are assumed to be filterable and all samplers are assumed to be filtering. This may lead to compatibility issues. This can usually be resolved by requesting the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES.
  • It's possible to achieve slightly better performance than the generated code in some cases like avoiding redundant bind group bindings or adjusting resource shader stage visibility. This should be addressed by using some handwritten code where appropriate.

Publishing Crates

Rust expects build scripts to not modify files outside of OUT_DIR. The provided example project outputs the generated bindings to the src/ directory for documentation purposes. This approach is also fine for applications. Published crates should follow the recommendations for build scripts in the Cargo Book.

use wgsl_to_wgpu::{create_shader_module_embedded, WriteOptions};

// src/build.rs
fn main() {
    println!("cargo:rerun-if-changed=src/model.wgsl");

    // Generate the Rust bindings and write to a file.
    let text = create_shader_module_embedded(wgsl_source, WriteOptions::default()).unwrap();
    let out_dir = std::env::var("OUT_DIR").unwrap();
    std::fs::write(format!("{out_dir}/model.rs"), text.as_bytes()).unwrap();
}

The generated code will need to be included in one of the normal source files. This includes adding any nested modules as needed.

// src/shader.rs
pub mod model {
    include!(concat!(env!("OUT_DIR"), "/model.rs"));
}

wgsl_to_wgpu's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

wgsl_to_wgpu's Issues

Automatically add padding fields?

First of all great crate, it makes working with wgpu much easier. A question I have is generating unaligned struct fields

struct QuadInstanceData {
    points: array<vec2<f32>, 4>,
    color: vec3<f32>,
}

generates

...
pub struct QuadInstanceData {
    pub points: [[f32; 2]; 4],
    pub color: [f32; 3],
}
const _: () = assert!(
    std::mem::size_of:: < QuadInstanceData > () == 48,
    "size of QuadInstanceData does not match WGSL"
);
const _: () = assert!(
    memoffset::offset_of!(QuadInstanceData, points) == 0,
    "offset of QuadInstanceData.points does not match WGSL"
);
const _: () = assert!(
    memoffset::offset_of!(QuadInstanceData, color) == 32,
    "offset of QuadInstanceData.color does not match WGSL"
);

which fails with

const _: () = assert!(
   |  _______________^
18 | |     std::mem::size_of:: < QuadInstanceData > () == 48,
19 | |     "size of QuadInstanceData does not match WGSL"
20 | | );
   | |_^ the evaluated program panicked at 'size of QuadInstanceData does not match WGSL', test-wgpu/quad_shader.rs:17:15

Isn't it possible to automatically add the padding fields to the generated code instead such that it is always correct? And it is upto the user to either use MaybeUnit or zero out the padding fields. In this case

pub struct QuadInstanceData {
    pub points: [[f32; 2]; 4],
    pub color: [f32; 3],
    pub _pad: f32,
}

would fix the issue

Don't assume all bind groups are used in create_pipeline_layout

wgpu can detect the case where a bind group layout is not included in the pipeline layout and not used in the shader or used in the shader but not included in the pipeline layout. wgsl_to_wgpu assumes all bind groups are used, which can create problems for different pipelines in the same WGSL file. A depth only pass for shadows might not use the material uniforms buffer but still want to use the same vertex code as the color pass, for example.

This may require recreating some of the validation code used by naga or wgpu. It's also worth investigating how this works with non consecutive bind groups if index 1 is unused for bind groups 0, 1, 2.

Generate function that returns VertexBufferLayout for types

First of all, I love this crate, it cuts down on the boiler plate so much.

I have a small feature request:
there could be a function that returns a complete VertexBufferLayout for the generated types, and not just the attributes. It's somewhat repetitive to always create that struct instead of just calling a function a'la the Vertex::desc() function in the wgpu tutorial.

Maybe the step_mode could be a parameter.

Add option to only derive bytemuck for vertex input structs

Some applications may want to use encase for storage and uniform buffers and only use bytemuck for vertex inputs due to the relaxed alignment requirements. It doesn't really make sense to use encase and apply storage or uniform layout rules to vertex inputs. Vertex inputs also used as uniform or storage buffer types should still support deriving both bytemuck and encase if possible.

Using encase for host shareable types and bytemuck for vertex inputs should be the "recommended" way to use this library. The example project should be updated to show how to use encase with UniformBuffer, StorageBuffer and DynamicStorageBuffer (aligns offsets to 256).

Failing on TODO: is it possible to have builtins for inputs?

struct QuadInstanceData {
    points: array<vec2<f32>, 4>,
    color: vec3<f32>,
    _pad: f32
}

@group(0)
@binding(0)
var<storage, read> instances: array<QuadInstanceData>;

struct VertexInput {
    @builtin(vertex_index) vid: u32,
    @builtin(instance_index) instance: u32
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) color: vec3<f32>,
};

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
    var quad = instances[in.instance];
    let position = quad.points[in.vid];

    var out: VertexOutput;
    out.clip_position = vec4<f32>(position, 0.0, 1.0);
    out.color = quad.color;
    return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    return vec4<f32>(in.color, 1.0);
}

It appears that this is failing when generating the rust file, but I can get it to work manually using the exact source. As a workaround, I have to instead write

fn vs_main(@builtin(position) clip_position: vec4<f32>, @location(0) color: vec3<f32>)`
thread 'main' panicked at /Users/swoorup.joshi/.cargo/registry/src/index.crates.io-6f17d22bba15001f/wgsl_to_wgpu-0.5.0/src/wgsl.rs:248:74:
  not yet implemented
  stack backtrace:
     0:        0x1006c88ac - std::backtrace_rs::backtrace::libunwind::trace::hb94b1b273dfca588
                                 at /rustc/5518eaa946291f00471af8b254b2a1715f234882/library/std/src/../../backtrace/src/backtrace/libunwind.rs:104:5
     1:        0x1006c88ac - std::backtrace_rs::backtrace::trace_unsynchronized::h7cf7a82627f86fd0
                                 at /rustc/5518eaa946291f00471af8b254b2a1715f234882/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
     2:        0x1006c88ac - std::sys_common::backtrace::_print_fmt::hebf7e9dbe0102c18

Infer bind group visibility from entry points

The current implementation assumes one shader file for all entry points. Entry points have an associated ShaderStage, so the visibility can be inferred. There may be issues with some resources like SSBOs not working with all stages.

  • set visibility to available shader stages in module
  • check visibility for SSBOs

support wgpu vertex formats

These types should all be usable with vertex input structs for WGSL. The test cases in lib.rs for writing the vertex module should cover all of the supported types.
https://docs.rs/wgpu/latest/wgpu/enum.VertexFormat.html

  • wgpu_types::VertexFormat::Uint8x2
  • wgpu_types::VertexFormat::Uint8x4
  • wgpu_types::VertexFormat::Sint8x2
  • wgpu_types::VertexFormat::Sint8x4
  • wgpu_types::VertexFormat::Unorm8x2
  • wgpu_types::VertexFormat::Unorm8x4
  • wgpu_types::VertexFormat::Snorm8x2
  • wgpu_types::VertexFormat::Snorm8x4
  • wgpu_types::VertexFormat::Uint16x2
  • wgpu_types::VertexFormat::Uint16x4
  • wgpu_types::VertexFormat::Sint16x2
  • wgpu_types::VertexFormat::Sint16x4
  • wgpu_types::VertexFormat::Unorm16x2
  • wgpu_types::VertexFormat::Unorm16x4
  • wgpu_types::VertexFormat::Snorm16x2
  • wgpu_types::VertexFormat::Snorm16x4
  • wgpu_types::VertexFormat::Float16x2
  • wgpu_types::VertexFormat::Float16x4
  • wgpu_types::VertexFormat::Float32
  • wgpu_types::VertexFormat::Float32x2
  • wgpu_types::VertexFormat::Float32x3
  • wgpu_types::VertexFormat::Float32x4
  • wgpu_types::VertexFormat::Uint32
  • wgpu_types::VertexFormat::Uint32x2
  • wgpu_types::VertexFormat::Uint32x3
  • wgpu_types::VertexFormat::Uint32x4
  • wgpu_types::VertexFormat::Sint32
  • wgpu_types::VertexFormat::Sint32x2
  • wgpu_types::VertexFormat::Sint32x3
  • wgpu_types::VertexFormat::Sint32x4
  • wgpu_types::VertexFormat::Float64
  • wgpu_types::VertexFormat::Float64x2
  • wgpu_types::VertexFormat::Float64x3
  • wgpu_types::VertexFormat::Float64x4

support depth textures

The layout descriptors should have sample_type: wgpu::TextureSampleType::Depth. In addition, samplers with comparison functions should be supported.

Don't assume that all textures are filterable

I've ran into this lovely TODO:

// TODO: Don't assume all textures are filterable.
quote!(wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },

I'm trying to use R32Float texture views in a compute shader (with a Nearest filter in the sampler), but this code path does not let me. Unfortunately it's not clear at all how this could be made better, since there is no standard way of marking a texture non-filterable in WGSL.

I'd rather not fall back to manually altering bind group descriptors and creating bind groups.

`set_bind_groups` is not a method of `BindGroups`

set_bind_groups could be turned into a method of BindGroups

For example:

pub fn set_bind_groups<'a>(
    pass: &mut wgpu::RenderPass<'a>,
    bind_groups: BindGroups<'a>,
) {
    bind_groups.bind_group0.set(pass);
    bind_groups.bind_group1.set(pass);
}

->

impl<'a> BindGroups<'a> {
    // Assuming BindGroups is Copy
    pub fn set(self, &mut wgpu::RenderPass<'a>) {
        self.bind_group0.set(pass);
        self.bind_group1.set(pass);
    }
}

Support runtime-sized arrays

Let's say I have the following WGSL code, which is then used by a compute shader:

struct Data {
    some_param_1: u32,
    some_param_2: u32,
    data: array<u32>,
}
@group(0) @binding(0)
var <storage, read_write> data: Data;

The way this works is that WGSL will calculate the actual array length from the binding size.

Unfortunately wgsl_to_wgpu generates a zero length array from this, which makes encase unhappy and is also kind of unusable Rust-side. Fortunately encase has functionality to handle this case; by marking the field with the #[size(runtime)] pseuodo-attribute. Then the field can be a Vec and that is correctly handled by the encase read and write functions.

Of course this breaks bytemuck or raw casting, but the current zero-length array thing doesn't work that well anyway.

Now the question is, is it a good idea to special-case this (derive_encase == true and the array is runtime-sized)? Or something else should be done? I'd be happy to make a PR, but not until we agree on a solution.

Support compute shaders

  • dynamically sized arrays
  • storage buffer objects
  • workgroup size
  • set bind groups on ComputePass instead of RenderPass
  • set compute pass

Setup CI

Setting up CI would be great!
Happy to make a PR with the following:

  • Check build
  • Run tests
  • Build examples
  • Clippy
  • Rustfmt
  • RustSec
  • Dependabot (technically not CI, but while I would be at it)

iterate through bind_groups

Is it possibel with current implementation to somehow loop through bind_groups when binding buffers to them? Atm I have a hard time figuring out how to avoid doing manually BindingGroup0, BindingGroup1 etc.

Improve example project

The current example project lacks code demonstrating these features.

  • vertex input structs
  • compute shaders
  • uniform buffers
  • storage buffers + encase

nalgebra: `ShaderSize` is not implemented for `Matrix<f32, Const<3>...`

I just hit a size issue caused by:

struct Material {
    color: vec4<f32>
}

@group(0)
@binding(0)
var<uniform> material: Material;
thread 'main' panicked at 'wgpu error: Validation Error

Caused by:
    In a RenderPass
      note: encoder = `<CommandBuffer-(0, 1, Vulkan)>`
    In a draw command, indexed:false indirect:false
      note: render pipeline = `Render Pipeline`
    Buffer is bound with size 12 where the shader expects 16 in group[0] compact index 0

But when I try to enable derive_encase in the WriteOptions, I get the following error for a:

error[E0277]: the trait bound `Matrix<f32, Const<3>, Const<1>, ArrayStorage<f32, 3, 1>>: ShaderSize` is not satisfied
  --> src\shaders\shader.rs:11:5
   |
11 |     encase::ShaderType
   |     ^^^^^^^^^^^^^^^^^^ the trait `ShaderSize` is not implemented for `Matrix<f32, Const<3>, Const<1>, ArrayStorage<f32, 3, 1>>`
   |
   = help: the following other types implement trait `ShaderSize`:
             &T
             &mut T
             Arc<T>
             ArrayLength
             AtomicI32
             AtomicU32
             Box<T>
             Cow<'_, T>
           and 47 others
   = help: see issue #48214
   = note: this error originates in the derive macro `encase::ShaderType` (in Nightly builds, run with -Z macro-backtrace for more info)

Please advise.

Make this a procedural macro.

I believe this could just be a procedural macro. Something like:

include_shader!("src/shader.wgsl");

This could write the wgsl to the OUT_DIR so the generated rs is only in the output directory (not committed to git for example). And now all users would have to do is call that macro for every shader they want to use.

Don't generate structs that aren't needed from Rust

Users will only need to initialize host shareable or vertex input structs on the Rust side. This can replace the existing check if the type is an output of a shader stage. This allows users to avoid potential derive errors on structs used only to store data internally in a shader.

Typesafe initialization with BindGroup0::from_data()

Hi there!

Great project!

As someone who struggled a lot to have proper shader <=> CPU bindings, I really love the idea to leverage Rust code generation + strong typing to make this easier.

To make it even easier, would it make sense to implement a BindGroup0::from_data() method relying on encase to create the bind group directly from the uniform data:

             pub fn from_data(device: &wgpu::Device, #(#fields),*) -> Self {
                use wgpu::util::DeviceExt;
                use encase::ShaderType;

                let mut buffer = encase::DynamicUniformBuffer::new(Vec::new());
                #(
                    buffer.write(#field_names).unwrap();
                    buffer.set_offset(device.limits().min_uniform_buffer_offset_alignment as u64);
                )*
                let bytes = buffer.into_inner();
                let uniform_bufer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                    label: None,
                    contents: &bytes,
                    usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                });
                
                Self::from_bindings(
                    device,
                    #bind_group_layout_name {
                        #(#bindings),*
                    },
                )
            }

Example of output:

        pub fn from_data(
            device: &wgpu::Device,
            material: &super::Material,
            alpha: &super::Alpha,
        ) -> Self {
            use wgpu::util::DeviceExt;
            use encase::ShaderType;
            let mut buffer = encase::DynamicUniformBuffer::new(Vec::new());
            buffer.write(material).unwrap();
            buffer
                .set_offset(device.limits().min_uniform_buffer_offset_alignment as u64);
            buffer.write(alpha).unwrap();
            buffer
                .set_offset(device.limits().min_uniform_buffer_offset_alignment as u64);
            let bytes = buffer.into_inner();
            let uniform_bufer = device
                .create_buffer_init(
                    &wgpu::util::BufferInitDescriptor {
                        label: None,
                        contents: &bytes,
                        usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                    },
                );
            Self::from_bindings(
                device,
                BindGroupLayout0 {
                    material: wgpu::BufferBinding {
                        buffer: &uniform_bufer,
                        offset: 0,
                        size: Some(super::Material::min_size()),
                    },
                    alpha: wgpu::BufferBinding {
                        buffer: &uniform_bufer,
                        offset: (1u32
                            * device.limits().min_uniform_buffer_offset_alignment)
                            as u64,
                        size: Some(super::Alpha::min_size()),
                    },
                },
            )
        }

Usage example:

let material = shader::Material {
  color: nalgebra::Vector3::new(1.0, 1.0, 0.0),
};
let alpha = shader::Alpha { value: 1.0 };
let bind_group0 = shader::bind_groups::BindGroup0::from_data(&device, &material, &alpha);

If that makes sense I'll open a PR.

Add option to use glam or Rust types

The current implementation uses a mix of standard Rust types like [f32; 4] and glam types like glam::Mat4. It should be possible to use this library without glam.

Switch to `quote` and use `prettyplease`

I think it would be a good additions to use quote instead of writing code into unchecked strings. It also alleviates the effort of having to format the code by hand, as prettyplease can do that for us.

From a dependency perspective there isn't any difference. naga already pulls in syn & co and prettyplease uses the same dependencies.

Happy to do a PR upon green-lighting.

WGSL preprocessor and modules

I'm planning on introducing WGSL preprocessing to one of my projects, as complexity is quickly getting out of hand.

What I'm looking at right now is bevy's syntax, since it's supported by wgsl analyzer. It could be implemented as a separate crate, or, since it's such a simple piece of code, it could probably be added to wgsl_to_wgpu directly too. It could be interesting for types especially: #imports could probably be turned into use declarations in the generated rust code in the future.

Alternatives:

  • There's the wgsl_preprocessor crate, but I'm not sure how widespread it is.
  • There are also some general purpose preprocessors out there.
  • There's the option of doing the preprocessing as a build.rs step too. In this case, no modification to wgsl_to_wgpu is needed.

I'd be happy to cobble together a PR, but I wanted to get some second opinions here first.

Organize code generation

The main goal of the code is to convert wgsl code or shader code supported by naga into Rust code. The process is
wgsl -> naga -> wgsl_to_wgpu types -> Rust code. This gives two distinct components of shader code -> wgsl_to_wgpu structs and then converting these structs to Rust code. This should also facilitate testing and potentially converting to being used from a build script later.

overly strict layout validation of wgsl vertex input struct

I have this struct in my shader:

struct Vertex {
    @location(0) position: vec3<f32>,
    @location(1) normal: vec3<f32>,
    @location(2) tangent: vec4<f32>,
    @location(3) tex_coords: vec2<f32>,
}

and wgsl_to_wgpu generates these asserts:

const _: () = assert!(
    std::mem::size_of::<Vertex>() == 64,
    "size of Vertex does not match WGSL"
);
const _: () = assert!(
    memoffset::offset_of!(Vertex, position) == 0,
    "offset of Vertex.position does not match WGSL"
);
const _: () = assert!(
    memoffset::offset_of!(Vertex, normal) == 16,
    "offset of Vertex.normal does not match WGSL"
);
const _: () = assert!(
    memoffset::offset_of!(Vertex, tangent) == 32,
    "offset of Vertex.tangent does not match WGSL"
);
const _: () = assert!(
    memoffset::offset_of!(Vertex, tex_coords) == 48,
    "offset of Vertex.tex_coords does not match WGSL"
);

Which except for the second one is incorrect.

I looked at the code for this, and it seems like an upstream problem with naga?
It might make sense to make the validation optional in that case.

Edit: the identical code was working when using an older version of this crate.

add example project

The API is very simple, so it makes more sense to document the project using an example. This can just be a simple example that draws a triangle to the screen showing how to simplify pipeline creation and creating and setting bind groups and other state.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.