shaman.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package renderer
  5. import (
  6. "fmt"
  7. "regexp"
  8. "strings"
  9. "github.com/g3n/engine/gls"
  10. "github.com/g3n/engine/material"
  11. "github.com/g3n/engine/renderer/shaders"
  12. "strconv"
  13. )
  14. const GLSL_VERSION = "330 core"
  15. // Regular expression to parse #include <name> [quantity] directive
  16. var rexInclude *regexp.Regexp
  17. const indexParameter = "{i}"
  18. func init() {
  19. rexInclude = regexp.MustCompile(`#include\s+<(.*)>\s*(?:\[(.*)]|)`)
  20. }
  21. // ShaderSpecs describes the specification of a compiled shader program
  22. type ShaderSpecs struct {
  23. Name string // Shader name
  24. Version string // GLSL version
  25. ShaderUnique bool // indicates if shader is independent of lights and textures
  26. UseLights material.UseLights // Bitmask indicating which lights to consider
  27. AmbientLightsMax int // Current number of ambient lights
  28. DirLightsMax int // Current Number of directional lights
  29. PointLightsMax int // Current Number of point lights
  30. SpotLightsMax int // Current Number of spot lights
  31. MatTexturesMax int // Current Number of material textures
  32. Defines gls.ShaderDefines // Additional shader defines
  33. }
  34. // ProgSpecs represents a compiled shader program along with its specs
  35. type ProgSpecs struct {
  36. program *gls.Program // program object
  37. specs ShaderSpecs // associated specs
  38. }
  39. // Shaman is the shader manager
  40. type Shaman struct {
  41. gs *gls.GLS
  42. includes map[string]string // include files sources
  43. shadersm map[string]string // maps shader name to its template
  44. proginfo map[string]shaders.ProgramInfo // maps name of the program to ProgramInfo
  45. programs []ProgSpecs // list of compiled programs with specs
  46. specs ShaderSpecs // Current shader specs
  47. }
  48. // NewShaman creates and returns a pointer to a new shader manager
  49. func NewShaman(gs *gls.GLS) *Shaman {
  50. sm := new(Shaman)
  51. sm.Init(gs)
  52. return sm
  53. }
  54. // Init initializes the shader manager
  55. func (sm *Shaman) Init(gs *gls.GLS) {
  56. sm.gs = gs
  57. sm.includes = make(map[string]string)
  58. sm.shadersm = make(map[string]string)
  59. sm.proginfo = make(map[string]shaders.ProgramInfo)
  60. }
  61. // AddDefaultShaders adds to this shader manager all default
  62. // include chunks, shaders and programs statically registered.
  63. func (sm *Shaman) AddDefaultShaders() error {
  64. for _, name := range shaders.Includes() {
  65. sm.AddChunk(name, shaders.IncludeSource(name))
  66. }
  67. for _, name := range shaders.Shaders() {
  68. sm.AddShader(name, shaders.ShaderSource(name))
  69. }
  70. for _, name := range shaders.Programs() {
  71. sm.proginfo[name] = shaders.GetProgramInfo(name)
  72. }
  73. return nil
  74. }
  75. // AddChunk adds a shader chunk with the specified name and source code
  76. func (sm *Shaman) AddChunk(name, source string) {
  77. sm.includes[name] = source
  78. }
  79. // AddShader adds a shader program with the specified name and source code
  80. func (sm *Shaman) AddShader(name, source string) {
  81. sm.shadersm[name] = source
  82. }
  83. // AddProgram adds a program with the specified name and associated vertex
  84. // and fragment shaders names (previously registered)
  85. func (sm *Shaman) AddProgram(name, vertexName, fragName string, others ...string) {
  86. geomName := ""
  87. if len(others) > 0 {
  88. geomName = others[0]
  89. }
  90. sm.proginfo[name] = shaders.ProgramInfo{
  91. Vertex: vertexName,
  92. Fragment: fragName,
  93. Geometry: geomName,
  94. }
  95. }
  96. // SetProgram sets the shader program to satisfy the specified specs.
  97. // Returns an indication if the current shader has changed and a possible error
  98. // when creating a new shader program.
  99. // Receives a copy of the specs because it changes the fields which specify the
  100. // number of lights depending on the UseLights flags.
  101. func (sm *Shaman) SetProgram(s *ShaderSpecs) (bool, error) {
  102. // Checks material use lights bit mask
  103. var specs ShaderSpecs
  104. specs.copy(s)
  105. if (specs.UseLights & material.UseLightAmbient) == 0 {
  106. specs.AmbientLightsMax = 0
  107. }
  108. if (specs.UseLights & material.UseLightDirectional) == 0 {
  109. specs.DirLightsMax = 0
  110. }
  111. if (specs.UseLights & material.UseLightPoint) == 0 {
  112. specs.PointLightsMax = 0
  113. }
  114. if (specs.UseLights & material.UseLightSpot) == 0 {
  115. specs.SpotLightsMax = 0
  116. }
  117. // If current shader specs are the same as the specified specs, nothing to do.
  118. if sm.specs.equals(&specs) {
  119. return false, nil
  120. }
  121. // Search for compiled program with the specified specs
  122. for _, pinfo := range sm.programs {
  123. if pinfo.specs.equals(&specs) {
  124. sm.gs.UseProgram(pinfo.program)
  125. sm.specs = specs
  126. return true, nil
  127. }
  128. }
  129. // Generates new program with the specified specs
  130. prog, err := sm.GenProgram(&specs)
  131. if err != nil {
  132. return false, err
  133. }
  134. log.Debug("Created new shader:%v", specs.Name)
  135. // Save specs as current specs, adds new program to the list and activates the program
  136. sm.specs = specs
  137. sm.programs = append(sm.programs, ProgSpecs{prog, specs})
  138. sm.gs.UseProgram(prog)
  139. return true, nil
  140. }
  141. // GenProgram generates shader program from the specified specs
  142. func (sm *Shaman) GenProgram(specs *ShaderSpecs) (*gls.Program, error) {
  143. // Get info for the specified shader program
  144. progInfo, ok := sm.proginfo[specs.Name]
  145. if !ok {
  146. return nil, fmt.Errorf("Program:%s not found", specs.Name)
  147. }
  148. // Sets the defines map
  149. defines := map[string]string{}
  150. defines["AMB_LIGHTS"] = strconv.Itoa(specs.AmbientLightsMax)
  151. defines["DIR_LIGHTS"] = strconv.Itoa(specs.DirLightsMax)
  152. defines["POINT_LIGHTS"] = strconv.Itoa(specs.PointLightsMax)
  153. defines["SPOT_LIGHTS"] = strconv.Itoa(specs.SpotLightsMax)
  154. defines["MAT_TEXTURES"] = strconv.Itoa(specs.MatTexturesMax)
  155. // Adds additional material and geometry defines from the specs parameter
  156. for name, value := range specs.Defines {
  157. defines[name] = value
  158. }
  159. // Get vertex shader source
  160. vertexSource, ok := sm.shadersm[progInfo.Vertex]
  161. if !ok {
  162. return nil, fmt.Errorf("Vertex shader:%s not found", progInfo.Vertex)
  163. }
  164. // Pre-process vertex shader source
  165. vertexSource, err := sm.preprocess(vertexSource, defines)
  166. if err != nil {
  167. return nil, err
  168. }
  169. //fmt.Printf("vertexSource:%s\n", vertexSource)
  170. // Get fragment shader source
  171. fragSource, ok := sm.shadersm[progInfo.Fragment]
  172. if err != nil {
  173. return nil, fmt.Errorf("Fragment shader:%s not found", progInfo.Fragment)
  174. }
  175. // Pre-process fragment shader source
  176. fragSource, err = sm.preprocess(fragSource, defines)
  177. if err != nil {
  178. return nil, err
  179. }
  180. //fmt.Printf("fragSource:%s\n", fragSource)
  181. // Checks for optional geometry shader compiled template
  182. var geomSource = ""
  183. if progInfo.Geometry != "" {
  184. // Get geometry shader source
  185. geomSource, ok = sm.shadersm[progInfo.Geometry]
  186. if !ok {
  187. return nil, fmt.Errorf("Geometry shader:%s not found", progInfo.Geometry)
  188. }
  189. // Pre-process geometry shader source
  190. geomSource, err = sm.preprocess(geomSource, defines)
  191. if err != nil {
  192. return nil, err
  193. }
  194. }
  195. // Creates shader program
  196. prog := sm.gs.NewProgram()
  197. prog.AddShader(gls.VERTEX_SHADER, vertexSource)
  198. prog.AddShader(gls.FRAGMENT_SHADER, fragSource)
  199. if progInfo.Geometry != "" {
  200. prog.AddShader(gls.GEOMETRY_SHADER, geomSource)
  201. }
  202. err = prog.Build()
  203. if err != nil {
  204. return nil, err
  205. }
  206. return prog, nil
  207. }
  208. func (sm *Shaman) preprocess(source string, defines map[string]string) (string, error) {
  209. // If defines map supplied, generate prefix with glsl version directive first,
  210. // followed by "#define" directives
  211. var prefix = ""
  212. if defines != nil { // This is only true for the outer call
  213. prefix = fmt.Sprintf("#version %s\n", GLSL_VERSION)
  214. for name, value := range defines {
  215. prefix = prefix + fmt.Sprintf("#define %s %s\n", name, value)
  216. }
  217. }
  218. return sm.processIncludes(prefix + source, defines)
  219. }
  220. // preprocess preprocesses the specified source prefixing it with optional defines directives
  221. // contained in "defines" parameter and replaces '#include <name>' directives
  222. // by the respective source code of include chunk of the specified name.
  223. // The included "files" are also processed recursively.
  224. func (sm *Shaman) processIncludes(source string, defines map[string]string) (string, error) {
  225. // Find all string submatches for the "#include <name>" directive
  226. matches := rexInclude.FindAllStringSubmatch(source, 100)
  227. if len(matches) == 0 {
  228. return source, nil
  229. }
  230. // For each directive found, replace the name by the respective include chunk source code
  231. //var newSource = source
  232. for _, m := range matches {
  233. incFullMatch := m[0]
  234. incName := m[1]
  235. incQuantityVariable := m[2]
  236. // Get the source of the include chunk with the match <name>
  237. incSource := sm.includes[incName]
  238. if len(incSource) == 0 {
  239. return "", fmt.Errorf("Include:[%s] not found", incName)
  240. }
  241. // Preprocess the include chunk source code
  242. incSource, err := sm.processIncludes(incSource, defines)
  243. if err != nil {
  244. return "", err
  245. }
  246. // Skip line
  247. incSource = "\n" + incSource
  248. // Process include quantity variable if provided
  249. if incQuantityVariable != "" {
  250. incQuantityString, defined := defines[incQuantityVariable]
  251. if defined { // Only process #include if quantity variable is defined
  252. incQuantity, err := strconv.Atoi(incQuantityString)
  253. if err != nil {
  254. return "", err
  255. }
  256. // Check for iterated includes and populate index parameter
  257. if incQuantity > 0 {
  258. repeatedIncludeSource := ""
  259. for i := 0; i < incQuantity; i++ {
  260. // Replace all occurrences of the index parameter with the current index i.
  261. repeatedIncludeSource += strings.Replace(incSource, indexParameter, strconv.Itoa(i), -1)
  262. }
  263. incSource = repeatedIncludeSource
  264. }
  265. } else {
  266. incSource = ""
  267. }
  268. }
  269. // Replace all occurrences of the include directive with its processed source code
  270. source = strings.Replace(source, incFullMatch, incSource, -1)
  271. }
  272. return source, nil
  273. }
  274. // copy copies other spec into this
  275. func (ss *ShaderSpecs) copy(other *ShaderSpecs) {
  276. *ss = *other
  277. if other.Defines != nil {
  278. ss.Defines = *gls.NewShaderDefines()
  279. ss.Defines.Add(&other.Defines)
  280. }
  281. }
  282. // equals compares two ShaderSpecs and returns true if they are effectively equal.
  283. func (ss *ShaderSpecs) equals(other *ShaderSpecs) bool {
  284. if ss.Name != other.Name {
  285. return false
  286. }
  287. if other.ShaderUnique {
  288. return true
  289. }
  290. if ss.AmbientLightsMax == other.AmbientLightsMax &&
  291. ss.DirLightsMax == other.DirLightsMax &&
  292. ss.PointLightsMax == other.PointLightsMax &&
  293. ss.SpotLightsMax == other.SpotLightsMax &&
  294. ss.MatTexturesMax == other.MatTexturesMax &&
  295. ss.Defines.Equals(&other.Defines) {
  296. return true
  297. }
  298. return false
  299. }