Front-end_dev

HTML5 Canvas Performance Optimazation Issue 본문

ES6/Computer Graphics

HTML5 Canvas Performance Optimazation Issue

Eat2go 2017. 5. 7. 17:21

말그대로 HTML5 캔버스 성능최적화 이슈에대해 쓴 글 입니다.


캔버스에서 그리기를 확정짓는 함수(stroke())를 특정 임의의 시간마다 호출하다가 Canvas자체 내부적인 최적화 시스템에 부딪히게되어 뜻대로 코드가 동작하지 않았습니다.


[아에 통째로 예제를 하나 만들어서 설명]


Main Issue : HTML5 Canvas 자체 내부에서 최적화시스템을 갖고있기 때문에 stroke()함수를 특정시간마다 n번 호출하면 바로바로 그려주지않고 맨마지막에 한꺼번에 그리는 문제.


part 1.  setTimeout 함수는 시간지연함수라고 보기엔 참 애매한포지션

part 1-1. 직접 시간지연함수를 만들어보자

part 2.  이 예제의 대한 기본적인 수학개념 

part 3. HTML5 캔버스자체 내부적인 최적화시스템에 부딪히다

part 3-1. 해결책 제시




part1. 

setTimeout()함수는 특정 시간후에 던져준 함수를 실행하는 함수이다. 근데 비동기적으로 실행하기때문에 코드 순서가 보장되지 않으며 많은 초보자들이 여기서 clojure에 부딪히게되고 힘들어한다. 

setTimeout()함수는 특정시간이 지난후에 호출한다고했지만 비동기적으로 실행된다고했다. 그래서 이 함수가 애매모호할 때 가 분명히 있다. 위에서말한거처럼 순서를 보장하면서 지연시키는 로직이필요한데 setTImeout()함수 단독으로는  그역할을 하지 못한다는것이다.  


part1-1.

직접 시간지연함수를 만들어볼수있다. 간단하다. 문제 : 특정 n초를 구현하고자 할 때 시간지연함수 만들기.


해결책1.

for(let i=0; i<10000000; i++) {
for(let j=0; j<1000000; j++) {
}

이렇게 구현 할 수도있긴한데 이건 CPU의 성능마다 다르게 동작할수있기 때문에 적절하지못하다

해결책2.
1
  while( (+new Date<= now+1000 ){} 
cs


코드에는 없지만 now변수가 미리 선언되있고 현재시각을 초기화받았다. 그리고 1초가흘러갈떄까지(now변수가생성되고나서1초의시간) delay하는 효과를 볼 수있는데 while scope안에 코드를 절대 적어주면 안된다. 저코드도
cpu를 바쁘게 움직이면서 지연하는 함수라서 만약 안에 code가있으면 그코드를 거의 무한적으로 실행하게 되어 프로그램이 뻗게된다. 가장 좋은 방법은 Javascript엔진 자체에서 함수를 제공해주면 좋겠는데 현재는 없다.


해결책3.
결론부터 말하자면 setTimeout()함수를 써서 조치하는것이다.
setTimeout함수에 던져준 함수에서 일종의 플래그를 변경하는코드가 들어있고, 그다음 로직에서는 그 플래그에 의존하여 실행하게끔 만들어주면 시간지연을 구현 할 수있다.

part 2.
이등변 삼각형에서 x,y좌표구하는 방법
y = sin(seta) * radius
x = cos(seta) * radius

지금부터예제코드를 보이면서 이예제애대해 설명하게 될 텐데 이예제는 미니사각형을 랜덤한방향(상하좌우)으로 퍼뜨려주는 예제이다.






x,y가있는 좌표에서 랜덤으로 방향을 설정해주고 , 특정시간마다 x,y만큼이동하여 사각형을 표시합니다

이때 x,y는 삼각함수를 통해서 구할수있습니다. seta는 0도에서 360도사이에 랜덤한숫자를 가집니다



part 3.

이제 이것을 실제로 코드로 해서 실행해봤는데 원하던대로 실행되지 않습니다. 처음에 말한 메인이슈처럼 캔버스자체에서 내부적으로 최적화시스템을돌리기때문에 stroke함수를 매번호출하는게아니라 한번에 싹다호출하는것이었습니다.


전체코드 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<body>
    <canvas id='canvas' width='500' height='500'/>
</body>
<script>
    let ctx = document.querySelector('canvas').getContext('2d');
    let rectInfo = {
        x : 200, y : 200, width : 50, height : 50
    };
    let backup = { x : 200, y : 200, width : 50, height : 50};
    ctx.strokeRect(rectInfo.x,rectInfo.y,rectInfo.width,rectInfo.height);
    setTimeout( () => {
        
        let entryPoint = {x : rectInfo.x/2, y : rectInfo.y/2 }; // 사각형이 분해되는 시작지점을 사각형의 중간으로잡음.
        let x,y,now,degree,interval = 0;
        // y = sin(seta) * r
        // x = cos(seta) * r
        ctx.strokeStyle = 'blue';
        for(let i=0; i<5; i++) {
            degree = Math.floor(Math.random() * 360+ 1;  // 0 ~ 360
            
            for(let j=0; j<5; j++) {
                now = +new Date;
                rectInfo.x += Math.cos(degree) * (rectInfo.width/2); // 방향을 잡아줌.
                rectInfo.y += Math.sin(degree) * (rectInfo.width/2); // 방향을 잡아줌.
                
                
                
                while( (+new Date<= now+1000 ){} // 1초 딜레이
                ctx.rect(rectInfo.x,rectInfo.y,20,20);
                ctx.stroke();
                
                          
                
            }
            rectInfo.x = backup.x;
            rectInfo.y = backup.y;
        }
    },2000);
</script>
</html>
 
cs


part 3-1. 

1초마다 stroke를 호출하는데 실제로는 25초후에 25번호출됩니다. 제가 원하는건 1초마다 1번 호출하는건데 생각을 조금바꿔서 코드를 다시 변형해봤습니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<body>
    <canvas id='canvas' width='500' height='500'/>
</body>
<script>
    let ctx = document.querySelector('canvas').getContext('2d');
 
    let rectInfo = {
        x : 200, y : 200, width : 50, height : 50
    };
    let backup = { x : 200, y : 200, width : 50, height : 50};
 
    ctx.strokeRect(rectInfo.x,rectInfo.y,rectInfo.width,rectInfo.height);
 
    setTimeout( () => {
        
        let entryPoint = {x : rectInfo.x/2, y : rectInfo.y/2 }; // 사각형이 분해되는 시작지점을 사각형의 중간으로잡음.
        let x,y,now,degree,interval = 0;
        // y = sin(seta) * r
        // x = cos(seta) * r
        ctx.strokeStyle = 'blue';
        for(let i=0; i<5; i++) {
            degree = Math.floor(Math.random() * 360+ 1;  // 0 ~ 360
            
 
            for(let j=0; j<5; j++) {
                now = +new Date;
                rectInfo.x += Math.cos(degree) * (rectInfo.width/2); // 방향을 잡아줌.
                rectInfo.y += Math.sin(degree) * (rectInfo.width/2); // 방향을 잡아줌.
                
                
                /*
                while( (+new Date) <= now+1000 ){} // 1초 딜레이
                ctx.rect(rectInfo.x,rectInfo.y,20,20);
                ctx.stroke();
                */
                interval += 1000;
                ( (x,y) => {
                    setTimeout( () => {
                        ctx.strokeRect(x,y,20,20);
                        console.log(x + y);
                    },interval);
                })(rectInfo.x,rectInfo.y);               
                
            }
            rectInfo.x = backup.x;
            rectInfo.y = backup.y;
        }
    },2000);
</script>
</html>
 
 
 
 
 
cs



사실상 논리자체는이전코드와 지그코드는 별반 다를게 없습니다. 근데 지금코드는 clojure를 이용해서 조치한 모습입니다.

앞으로 특정시간마다 stroke를 호출하는건 이렇게 코드를 살짝 변경(우회시켜서)해서 작성하면 될 것 같습니다. 


clojure를쓰지않고 while()로 시간지연함수를 구현했을때는 아마도 무수히많은 실행과 더불어 stroke를 호출해서 그런 것 같습니다. 하지만 setTimeout함수는 특정 시간후에 딱 한번만 실행하죠. 그차이인것같네요 

'ES6 > Computer Graphics' 카테고리의 다른 글

첫 증강현실 예제  (0) 2018.01.07
노멀벡터 계산할때  (0) 2017.12.08
webgl에서 bezier 곡선그리기  (0) 2017.10.31
2D Mesh  (0) 2017.08.25
Canvas에서 형광등효과 내보기.  (0) 2017.05.28